diff --git a/docs/Diplomamunka_Szabo_Tamas.docx b/docs/Diplomamunka_Szabo_Tamas.docx index 89171f2..dc83f9a 100644 Binary files a/docs/Diplomamunka_Szabo_Tamas.docx and b/docs/Diplomamunka_Szabo_Tamas.docx differ diff --git a/docs/draft.docx b/docs/draft.docx index 41e2f56..742e71d 100644 Binary files a/docs/draft.docx and b/docs/draft.docx differ diff --git a/grammar/MetaCode.g4 b/grammar/MetaCode.g4 index 5313412..dcca9aa 100644 --- a/grammar/MetaCode.g4 +++ b/grammar/MetaCode.g4 @@ -7,6 +7,7 @@ statements : (statement ';')+ ; statement : Expression=expression + | Return=returnStatement | Attributes=attributes? VariableDeclaration=variableDeclaration | Attributes=attributes? If=ifStatement | Attributes=attributes? Block=blockStatement @@ -39,21 +40,23 @@ expression : PrimaryExpression=primaryExpression functionCallExpression : primaryExpression '(' expression? ')' ; -memberExpression : primaryExpression ('.' (identifier | functionCallExpression))+ +memberExpression : (primaryExpression | functionCallExpression) ('.' memberTagExpression)+ ; +memberTagExpression : identifier | functionCallExpression; + primaryExpression : Attributes=attributes? Constant=constant - | Attributes=attributes? Id=identifier + | Attributes=attributes? Id=ID | Attributes=attributes? Function=functionExpression | Attributes=attributes? Assignment=assignmentExpression | Attributes=attributes? '(' InnerExpression=expression ')' ; -functionExpression : FUNCTION FunctionName=identifier? '(' Parameters=formalParameterList? ')' (':' ReturnType=typeName)? DO BodyStatements=statements END - | FUNCTION FunctionName=identifier? '(' Parameters=formalParameterList? ')' (':' ReturnType=typeName)? '=' BodyExpression=expression +functionExpression : FUNCTION FunctionName=ID? '(' Parameters=formalParameterList? ')' (':' ReturnType=typeName) DO BodyStatements=statements END + | FUNCTION FunctionName=ID? '(' Parameters=formalParameterList? ')' (':' ReturnType=typeName) '=' BodyExpression=expression ; -foreachStatement : FOREACH '(' Var=VAR? Id=identifier (':' VariableType=typeName)? IN ArrayExpression=expression ')' Body=statement +foreachStatement : FOREACH '(' Var=VAR? Id=ID (':' VariableType=typeName)? IN ArrayExpression=expression ')' Body=statement ; whileStatement : WHILE '(' ConditionExpression=expression ')' Body=statement @@ -64,22 +67,24 @@ blockStatement : DO Body=statements END skipStatement : SKIP; -assignmentExpression: Variable=identifier ASSIGN Value=expression (ConditionalAttributes=attributes? IF '(' ConditionalExpression=expression ')')? +returnStatement : RETURN expression; + +assignmentExpression: Variable=ID ASSIGN Value=expression (ConditionalAttributes=attributes? IF '(' ConditionalExpression=expression ')')? ; ifStatement : IF '(' Condition=expression ')' Statements=statements - ElseIfExpressions=elseIfStatement* + ElseIfStatements=elseIfStatement* (ELSE ElseStatements=statements)? END ; -elseIfStatement : ELSE IF '(' expression ')' statements +elseIfStatement : ELSE IF '(' Condition=expression ')' Statements=statements ; formalParameterList : formalParameter (',' formalParameter)* ; -formalParameter : attributes? identifier ':' typeName +formalParameter : Attributes=attributes? Name=ID ':' Type=typeName ; actualParameterList : expression (',' expression)* @@ -155,6 +160,8 @@ NOT : 'not'; NULL : 'null'; +RETURN : 'return'; + LEFT_PARENTHESIS : '('; RIGHT_PARENTHESIS : ')'; diff --git a/grammar/example.txt b/grammar/example.txt index 14bcaa1..ef08d90 100644 --- a/grammar/example.txt +++ b/grammar/example.txt @@ -31,6 +31,10 @@ else end; */ +(5 + 5).ToString(); + +var a = ToString().Parse("Hello World!").Hiii; + var a : System.Boolean = false; foreach (var i: Integer in 1 to 100 by 34) do @@ -41,7 +45,7 @@ if (not (@convert false)) @cast("System.Int32") (0 to 3 by 2); end; -@tail-recursive(false) function max(a: Integer, b: Integer) do +@tail-recursive(false) function max(a: Integer, b: Integer): Integer do if (a < b) a; else diff --git a/grammar/src/MetaCode.tokens b/grammar/src/MetaCode.tokens index 7b17149..e763b7a 100644 --- a/grammar/src/MetaCode.tokens +++ b/grammar/src/MetaCode.tokens @@ -2,12 +2,12 @@ FUNCTION=19 WHILE=21 NULL=34 ELSE=23 -NUMBER=42 -ATTRIBUTE_ID=40 -WHITESPACE=43 +NUMBER=43 +ATTRIBUTE_ID=41 +WHITESPACE=44 DO=24 NOT=33 -ID=37 +ID=38 AND=31 T__9=9 T__8=10 @@ -17,31 +17,32 @@ T__5=13 IF=22 SKIP=27 T__4=14 -MULTILINE_COMMENT=39 +MULTILINE_COMMENT=40 BOOLEAN=26 T__16=2 IN=29 T__15=3 -NEWLINE=44 -RIGHT_PARENTHESIS=36 +NEWLINE=45 +RIGHT_PARENTHESIS=37 T__17=1 T__12=6 T__11=7 T__14=4 -LEFT_PARENTHESIS=35 +LEFT_PARENTHESIS=36 T__13=5 T__1=17 T__0=18 OR=32 T__10=8 T__3=15 +RETURN=35 ASSIGN=30 T__2=16 VAR=28 FOREACH=20 -COMMENT=38 +COMMENT=39 END=25 -STRING=41 +STRING=42 'foreach'=20 'end'=25 '>='=18 @@ -50,9 +51,10 @@ STRING=41 'null'=34 '>'=14 ';'=11 +'return'=35 '='=30 '+'=4 -')'=36 +')'=37 '.'=2 'function'=19 'do'=24 @@ -66,7 +68,7 @@ STRING=41 '!='=10 '<'=9 'if'=22 -'('=35 +'('=36 'not'=33 ':'=8 'or'=32 diff --git a/grammar/src/MetaCodeBaseListener.class b/grammar/src/MetaCodeBaseListener.class index d0b3e7b..99ce853 100644 Binary files a/grammar/src/MetaCodeBaseListener.class and b/grammar/src/MetaCodeBaseListener.class differ diff --git a/grammar/src/MetaCodeBaseListener.java b/grammar/src/MetaCodeBaseListener.java index 123740e..c6a0ada 100644 --- a/grammar/src/MetaCodeBaseListener.java +++ b/grammar/src/MetaCodeBaseListener.java @@ -1,4 +1,4 @@ -// Generated from ../MetaCode.g4 by ANTLR 4.2 +// Generated from ../MetaCode.g4 by ANTLR 4.1 import org.antlr.v4.runtime.ParserRuleContext; import org.antlr.v4.runtime.misc.NotNull; @@ -13,403 +13,429 @@ public class MetaCodeBaseListener implements MetaCodeListener { /** * {@inheritDoc} - * - *

The default implementation does nothing.

+ *

+ * The default implementation does nothing. */ @Override public void enterExpression(@NotNull MetaCodeParser.ExpressionContext ctx) { } /** * {@inheritDoc} - * - *

The default implementation does nothing.

+ *

+ * The default implementation does nothing. */ @Override public void exitExpression(@NotNull MetaCodeParser.ExpressionContext ctx) { } /** * {@inheritDoc} - * - *

The default implementation does nothing.

+ *

+ * The default implementation does nothing. */ @Override public void enterFormalParameter(@NotNull MetaCodeParser.FormalParameterContext ctx) { } /** * {@inheritDoc} - * - *

The default implementation does nothing.

+ *

+ * The default implementation does nothing. */ @Override public void exitFormalParameter(@NotNull MetaCodeParser.FormalParameterContext ctx) { } /** * {@inheritDoc} - * - *

The default implementation does nothing.

+ *

+ * The default implementation does nothing. + */ + @Override public void enterReturnStatement(@NotNull MetaCodeParser.ReturnStatementContext ctx) { } + /** + * {@inheritDoc} + *

+ * The default implementation does nothing. + */ + @Override public void exitReturnStatement(@NotNull MetaCodeParser.ReturnStatementContext ctx) { } + + /** + * {@inheritDoc} + *

+ * The default implementation does nothing. */ @Override public void enterAttribute(@NotNull MetaCodeParser.AttributeContext ctx) { } /** * {@inheritDoc} - * - *

The default implementation does nothing.

+ *

+ * The default implementation does nothing. */ @Override public void exitAttribute(@NotNull MetaCodeParser.AttributeContext ctx) { } /** * {@inheritDoc} - * - *

The default implementation does nothing.

+ *

+ * The default implementation does nothing. */ @Override public void enterBlockStatement(@NotNull MetaCodeParser.BlockStatementContext ctx) { } /** * {@inheritDoc} - * - *

The default implementation does nothing.

+ *

+ * The default implementation does nothing. */ @Override public void exitBlockStatement(@NotNull MetaCodeParser.BlockStatementContext ctx) { } /** * {@inheritDoc} - * - *

The default implementation does nothing.

+ *

+ * The default implementation does nothing. */ @Override public void enterIntervalConstant(@NotNull MetaCodeParser.IntervalConstantContext ctx) { } /** * {@inheritDoc} - * - *

The default implementation does nothing.

+ *

+ * The default implementation does nothing. */ @Override public void exitIntervalConstant(@NotNull MetaCodeParser.IntervalConstantContext ctx) { } /** * {@inheritDoc} - * - *

The default implementation does nothing.

+ *

+ * The default implementation does nothing. */ @Override public void enterPrimaryExpression(@NotNull MetaCodeParser.PrimaryExpressionContext ctx) { } /** * {@inheritDoc} - * - *

The default implementation does nothing.

+ *

+ * The default implementation does nothing. */ @Override public void exitPrimaryExpression(@NotNull MetaCodeParser.PrimaryExpressionContext ctx) { } /** * {@inheritDoc} - * - *

The default implementation does nothing.

+ *

+ * The default implementation does nothing. */ @Override public void enterElseIfStatement(@NotNull MetaCodeParser.ElseIfStatementContext ctx) { } /** * {@inheritDoc} - * - *

The default implementation does nothing.

+ *

+ * The default implementation does nothing. */ @Override public void exitElseIfStatement(@NotNull MetaCodeParser.ElseIfStatementContext ctx) { } /** * {@inheritDoc} - * - *

The default implementation does nothing.

+ *

+ * The default implementation does nothing. */ @Override public void enterVariableDeclaration(@NotNull MetaCodeParser.VariableDeclarationContext ctx) { } /** * {@inheritDoc} - * - *

The default implementation does nothing.

+ *

+ * The default implementation does nothing. */ @Override public void exitVariableDeclaration(@NotNull MetaCodeParser.VariableDeclarationContext ctx) { } /** * {@inheritDoc} - * - *

The default implementation does nothing.

+ *

+ * The default implementation does nothing. */ @Override public void enterStatements(@NotNull MetaCodeParser.StatementsContext ctx) { } /** * {@inheritDoc} - * - *

The default implementation does nothing.

+ *

+ * The default implementation does nothing. */ @Override public void exitStatements(@NotNull MetaCodeParser.StatementsContext ctx) { } /** * {@inheritDoc} - * - *

The default implementation does nothing.

+ *

+ * The default implementation does nothing. */ @Override public void enterActualParameterList(@NotNull MetaCodeParser.ActualParameterListContext ctx) { } /** * {@inheritDoc} - * - *

The default implementation does nothing.

+ *

+ * The default implementation does nothing. */ @Override public void exitActualParameterList(@NotNull MetaCodeParser.ActualParameterListContext ctx) { } /** * {@inheritDoc} - * - *

The default implementation does nothing.

+ *

+ * The default implementation does nothing. */ @Override public void enterSkipStatement(@NotNull MetaCodeParser.SkipStatementContext ctx) { } /** * {@inheritDoc} - * - *

The default implementation does nothing.

+ *

+ * The default implementation does nothing. */ @Override public void exitSkipStatement(@NotNull MetaCodeParser.SkipStatementContext ctx) { } /** * {@inheritDoc} - * - *

The default implementation does nothing.

+ *

+ * The default implementation does nothing. */ @Override public void enterFormalParameterList(@NotNull MetaCodeParser.FormalParameterListContext ctx) { } /** * {@inheritDoc} - * - *

The default implementation does nothing.

+ *

+ * The default implementation does nothing. */ @Override public void exitFormalParameterList(@NotNull MetaCodeParser.FormalParameterListContext ctx) { } /** * {@inheritDoc} - * - *

The default implementation does nothing.

+ *

+ * The default implementation does nothing. */ @Override public void enterFunctionExpression(@NotNull MetaCodeParser.FunctionExpressionContext ctx) { } /** * {@inheritDoc} - * - *

The default implementation does nothing.

+ *

+ * The default implementation does nothing. */ @Override public void exitFunctionExpression(@NotNull MetaCodeParser.FunctionExpressionContext ctx) { } /** * {@inheritDoc} - * - *

The default implementation does nothing.

+ *

+ * The default implementation does nothing. */ @Override public void enterConstant(@NotNull MetaCodeParser.ConstantContext ctx) { } /** * {@inheritDoc} - * - *

The default implementation does nothing.

+ *

+ * The default implementation does nothing. */ @Override public void exitConstant(@NotNull MetaCodeParser.ConstantContext ctx) { } /** * {@inheritDoc} - * - *

The default implementation does nothing.

+ *

+ * The default implementation does nothing. */ @Override public void enterBooleanConstant(@NotNull MetaCodeParser.BooleanConstantContext ctx) { } /** * {@inheritDoc} - * - *

The default implementation does nothing.

+ *

+ * The default implementation does nothing. */ @Override public void exitBooleanConstant(@NotNull MetaCodeParser.BooleanConstantContext ctx) { } /** * {@inheritDoc} - * - *

The default implementation does nothing.

+ *

+ * The default implementation does nothing. + */ + @Override public void enterMemberTagExpression(@NotNull MetaCodeParser.MemberTagExpressionContext ctx) { } + /** + * {@inheritDoc} + *

+ * The default implementation does nothing. + */ + @Override public void exitMemberTagExpression(@NotNull MetaCodeParser.MemberTagExpressionContext ctx) { } + + /** + * {@inheritDoc} + *

+ * The default implementation does nothing. */ @Override public void enterAssignmentExpression(@NotNull MetaCodeParser.AssignmentExpressionContext ctx) { } /** * {@inheritDoc} - * - *

The default implementation does nothing.

+ *

+ * The default implementation does nothing. */ @Override public void exitAssignmentExpression(@NotNull MetaCodeParser.AssignmentExpressionContext ctx) { } /** * {@inheritDoc} - * - *

The default implementation does nothing.

+ *

+ * The default implementation does nothing. */ @Override public void enterInit(@NotNull MetaCodeParser.InitContext ctx) { } /** * {@inheritDoc} - * - *

The default implementation does nothing.

+ *

+ * The default implementation does nothing. */ @Override public void exitInit(@NotNull MetaCodeParser.InitContext ctx) { } /** * {@inheritDoc} - * - *

The default implementation does nothing.

+ *

+ * The default implementation does nothing. */ @Override public void enterNumberConstant(@NotNull MetaCodeParser.NumberConstantContext ctx) { } /** * {@inheritDoc} - * - *

The default implementation does nothing.

+ *

+ * The default implementation does nothing. */ @Override public void exitNumberConstant(@NotNull MetaCodeParser.NumberConstantContext ctx) { } /** * {@inheritDoc} - * - *

The default implementation does nothing.

+ *

+ * The default implementation does nothing. */ @Override public void enterIfStatement(@NotNull MetaCodeParser.IfStatementContext ctx) { } /** * {@inheritDoc} - * - *

The default implementation does nothing.

+ *

+ * The default implementation does nothing. */ @Override public void exitIfStatement(@NotNull MetaCodeParser.IfStatementContext ctx) { } /** * {@inheritDoc} - * - *

The default implementation does nothing.

+ *

+ * The default implementation does nothing. */ @Override public void enterMemberExpression(@NotNull MetaCodeParser.MemberExpressionContext ctx) { } /** * {@inheritDoc} - * - *

The default implementation does nothing.

+ *

+ * The default implementation does nothing. */ @Override public void exitMemberExpression(@NotNull MetaCodeParser.MemberExpressionContext ctx) { } /** * {@inheritDoc} - * - *

The default implementation does nothing.

+ *

+ * The default implementation does nothing. */ @Override public void enterStatement(@NotNull MetaCodeParser.StatementContext ctx) { } /** * {@inheritDoc} - * - *

The default implementation does nothing.

+ *

+ * The default implementation does nothing. */ @Override public void exitStatement(@NotNull MetaCodeParser.StatementContext ctx) { } /** * {@inheritDoc} - * - *

The default implementation does nothing.

+ *

+ * The default implementation does nothing. */ @Override public void enterTypeName(@NotNull MetaCodeParser.TypeNameContext ctx) { } /** * {@inheritDoc} - * - *

The default implementation does nothing.

+ *

+ * The default implementation does nothing. */ @Override public void exitTypeName(@NotNull MetaCodeParser.TypeNameContext ctx) { } /** * {@inheritDoc} - * - *

The default implementation does nothing.

+ *

+ * The default implementation does nothing. */ @Override public void enterWhileStatement(@NotNull MetaCodeParser.WhileStatementContext ctx) { } /** * {@inheritDoc} - * - *

The default implementation does nothing.

+ *

+ * The default implementation does nothing. */ @Override public void exitWhileStatement(@NotNull MetaCodeParser.WhileStatementContext ctx) { } /** * {@inheritDoc} - * - *

The default implementation does nothing.

+ *

+ * The default implementation does nothing. */ @Override public void enterFunctionCallExpression(@NotNull MetaCodeParser.FunctionCallExpressionContext ctx) { } /** * {@inheritDoc} - * - *

The default implementation does nothing.

+ *

+ * The default implementation does nothing. */ @Override public void exitFunctionCallExpression(@NotNull MetaCodeParser.FunctionCallExpressionContext ctx) { } /** * {@inheritDoc} - * - *

The default implementation does nothing.

+ *

+ * The default implementation does nothing. */ @Override public void enterAttributes(@NotNull MetaCodeParser.AttributesContext ctx) { } /** * {@inheritDoc} - * - *

The default implementation does nothing.

+ *

+ * The default implementation does nothing. */ @Override public void exitAttributes(@NotNull MetaCodeParser.AttributesContext ctx) { } /** * {@inheritDoc} - * - *

The default implementation does nothing.

+ *

+ * The default implementation does nothing. */ @Override public void enterArrayConstant(@NotNull MetaCodeParser.ArrayConstantContext ctx) { } /** * {@inheritDoc} - * - *

The default implementation does nothing.

+ *

+ * The default implementation does nothing. */ @Override public void exitArrayConstant(@NotNull MetaCodeParser.ArrayConstantContext ctx) { } /** * {@inheritDoc} - * - *

The default implementation does nothing.

+ *

+ * The default implementation does nothing. */ @Override public void enterForeachStatement(@NotNull MetaCodeParser.ForeachStatementContext ctx) { } /** * {@inheritDoc} - * - *

The default implementation does nothing.

+ *

+ * The default implementation does nothing. */ @Override public void exitForeachStatement(@NotNull MetaCodeParser.ForeachStatementContext ctx) { } /** * {@inheritDoc} - * - *

The default implementation does nothing.

+ *

+ * The default implementation does nothing. */ - @Override public void enterIdentifier(@NotNull MetaCodeParser.IdentifierContext ctx) { } + @Override public void enterStringConstant(@NotNull MetaCodeParser.StringConstantContext ctx) { } /** * {@inheritDoc} - * - *

The default implementation does nothing.

+ *

+ * The default implementation does nothing. */ - @Override public void exitIdentifier(@NotNull MetaCodeParser.IdentifierContext ctx) { } + @Override public void exitStringConstant(@NotNull MetaCodeParser.StringConstantContext ctx) { } /** * {@inheritDoc} - * - *

The default implementation does nothing.

+ *

+ * The default implementation does nothing. */ - @Override public void enterStringConstant(@NotNull MetaCodeParser.StringConstantContext ctx) { } + @Override public void enterIdentifier(@NotNull MetaCodeParser.IdentifierContext ctx) { } /** * {@inheritDoc} - * - *

The default implementation does nothing.

+ *

+ * The default implementation does nothing. */ - @Override public void exitStringConstant(@NotNull MetaCodeParser.StringConstantContext ctx) { } + @Override public void exitIdentifier(@NotNull MetaCodeParser.IdentifierContext ctx) { } /** * {@inheritDoc} - * - *

The default implementation does nothing.

+ *

+ * The default implementation does nothing. */ @Override public void enterEveryRule(@NotNull ParserRuleContext ctx) { } /** * {@inheritDoc} - * - *

The default implementation does nothing.

+ *

+ * The default implementation does nothing. */ @Override public void exitEveryRule(@NotNull ParserRuleContext ctx) { } /** * {@inheritDoc} - * - *

The default implementation does nothing.

+ *

+ * The default implementation does nothing. */ @Override public void visitTerminal(@NotNull TerminalNode node) { } /** * {@inheritDoc} - * - *

The default implementation does nothing.

+ *

+ * The default implementation does nothing. */ @Override public void visitErrorNode(@NotNull ErrorNode node) { } } \ No newline at end of file diff --git a/grammar/src/MetaCodeBaseVisitor.class b/grammar/src/MetaCodeBaseVisitor.class index 5bc2967..e2f7423 100644 Binary files a/grammar/src/MetaCodeBaseVisitor.class and b/grammar/src/MetaCodeBaseVisitor.class differ diff --git a/grammar/src/MetaCodeBaseVisitor.java b/grammar/src/MetaCodeBaseVisitor.java index e4ab29e..a71abe9 100644 --- a/grammar/src/MetaCodeBaseVisitor.java +++ b/grammar/src/MetaCodeBaseVisitor.java @@ -1,4 +1,4 @@ -// Generated from ../MetaCode.g4 by ANTLR 4.2 +// Generated from ../MetaCode.g4 by ANTLR 4.1 import org.antlr.v4.runtime.misc.NotNull; import org.antlr.v4.runtime.tree.AbstractParseTreeVisitor; @@ -13,233 +13,249 @@ public class MetaCodeBaseVisitor extends AbstractParseTreeVisitor implements MetaCodeVisitor { /** * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

+ *

+ * The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}. */ @Override public T visitExpression(@NotNull MetaCodeParser.ExpressionContext ctx) { return visitChildren(ctx); } /** * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

+ *

+ * The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}. */ @Override public T visitFormalParameter(@NotNull MetaCodeParser.FormalParameterContext ctx) { return visitChildren(ctx); } /** * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

+ *

+ * The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}. + */ + @Override public T visitReturnStatement(@NotNull MetaCodeParser.ReturnStatementContext ctx) { return visitChildren(ctx); } + + /** + * {@inheritDoc} + *

+ * The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}. */ @Override public T visitAttribute(@NotNull MetaCodeParser.AttributeContext ctx) { return visitChildren(ctx); } /** * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

+ *

+ * The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}. */ @Override public T visitBlockStatement(@NotNull MetaCodeParser.BlockStatementContext ctx) { return visitChildren(ctx); } /** * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

+ *

+ * The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}. */ @Override public T visitIntervalConstant(@NotNull MetaCodeParser.IntervalConstantContext ctx) { return visitChildren(ctx); } /** * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

+ *

+ * The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}. */ @Override public T visitPrimaryExpression(@NotNull MetaCodeParser.PrimaryExpressionContext ctx) { return visitChildren(ctx); } /** * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

+ *

+ * The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}. */ @Override public T visitElseIfStatement(@NotNull MetaCodeParser.ElseIfStatementContext ctx) { return visitChildren(ctx); } /** * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

+ *

+ * The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}. */ @Override public T visitVariableDeclaration(@NotNull MetaCodeParser.VariableDeclarationContext ctx) { return visitChildren(ctx); } /** * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

+ *

+ * The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}. */ @Override public T visitStatements(@NotNull MetaCodeParser.StatementsContext ctx) { return visitChildren(ctx); } /** * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

+ *

+ * The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}. */ @Override public T visitActualParameterList(@NotNull MetaCodeParser.ActualParameterListContext ctx) { return visitChildren(ctx); } /** * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

+ *

+ * The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}. */ @Override public T visitSkipStatement(@NotNull MetaCodeParser.SkipStatementContext ctx) { return visitChildren(ctx); } /** * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

+ *

+ * The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}. */ @Override public T visitFormalParameterList(@NotNull MetaCodeParser.FormalParameterListContext ctx) { return visitChildren(ctx); } /** * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

+ *

+ * The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}. */ @Override public T visitFunctionExpression(@NotNull MetaCodeParser.FunctionExpressionContext ctx) { return visitChildren(ctx); } /** * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

+ *

+ * The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}. */ @Override public T visitConstant(@NotNull MetaCodeParser.ConstantContext ctx) { return visitChildren(ctx); } /** * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

+ *

+ * The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}. */ @Override public T visitBooleanConstant(@NotNull MetaCodeParser.BooleanConstantContext ctx) { return visitChildren(ctx); } /** * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

+ *

+ * The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}. + */ + @Override public T visitMemberTagExpression(@NotNull MetaCodeParser.MemberTagExpressionContext ctx) { return visitChildren(ctx); } + + /** + * {@inheritDoc} + *

+ * The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}. */ @Override public T visitAssignmentExpression(@NotNull MetaCodeParser.AssignmentExpressionContext ctx) { return visitChildren(ctx); } /** * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

+ *

+ * The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}. */ @Override public T visitInit(@NotNull MetaCodeParser.InitContext ctx) { return visitChildren(ctx); } /** * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

+ *

+ * The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}. */ @Override public T visitNumberConstant(@NotNull MetaCodeParser.NumberConstantContext ctx) { return visitChildren(ctx); } /** * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

+ *

+ * The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}. */ @Override public T visitIfStatement(@NotNull MetaCodeParser.IfStatementContext ctx) { return visitChildren(ctx); } /** * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

+ *

+ * The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}. */ @Override public T visitMemberExpression(@NotNull MetaCodeParser.MemberExpressionContext ctx) { return visitChildren(ctx); } /** * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

+ *

+ * The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}. */ @Override public T visitStatement(@NotNull MetaCodeParser.StatementContext ctx) { return visitChildren(ctx); } /** * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

+ *

+ * The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}. */ @Override public T visitTypeName(@NotNull MetaCodeParser.TypeNameContext ctx) { return visitChildren(ctx); } /** * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

+ *

+ * The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}. */ @Override public T visitWhileStatement(@NotNull MetaCodeParser.WhileStatementContext ctx) { return visitChildren(ctx); } /** * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

+ *

+ * The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}. */ @Override public T visitFunctionCallExpression(@NotNull MetaCodeParser.FunctionCallExpressionContext ctx) { return visitChildren(ctx); } /** * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

+ *

+ * The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}. */ @Override public T visitAttributes(@NotNull MetaCodeParser.AttributesContext ctx) { return visitChildren(ctx); } /** * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

+ *

+ * The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}. */ @Override public T visitArrayConstant(@NotNull MetaCodeParser.ArrayConstantContext ctx) { return visitChildren(ctx); } /** * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

+ *

+ * The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}. */ @Override public T visitForeachStatement(@NotNull MetaCodeParser.ForeachStatementContext ctx) { return visitChildren(ctx); } /** * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

+ *

+ * The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}. */ - @Override public T visitIdentifier(@NotNull MetaCodeParser.IdentifierContext ctx) { return visitChildren(ctx); } + @Override public T visitStringConstant(@NotNull MetaCodeParser.StringConstantContext ctx) { return visitChildren(ctx); } /** * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

+ *

+ * The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}. */ - @Override public T visitStringConstant(@NotNull MetaCodeParser.StringConstantContext ctx) { return visitChildren(ctx); } + @Override public T visitIdentifier(@NotNull MetaCodeParser.IdentifierContext ctx) { return visitChildren(ctx); } } \ No newline at end of file diff --git a/grammar/src/MetaCodeLexer.class b/grammar/src/MetaCodeLexer.class index 805e81a..f39c7c9 100644 Binary files a/grammar/src/MetaCodeLexer.class and b/grammar/src/MetaCodeLexer.class differ diff --git a/grammar/src/MetaCodeLexer.java b/grammar/src/MetaCodeLexer.java index 5dff5e1..0bb4600 100644 --- a/grammar/src/MetaCodeLexer.java +++ b/grammar/src/MetaCodeLexer.java @@ -1,4 +1,4 @@ -// Generated from ../MetaCode.g4 by ANTLR 4.2 +// Generated from ../MetaCode.g4 by ANTLR 4.1 import org.antlr.v4.runtime.Lexer; import org.antlr.v4.runtime.CharStream; import org.antlr.v4.runtime.Token; @@ -18,9 +18,9 @@ public class MetaCodeLexer extends Lexer { T__9=9, T__8=10, T__7=11, T__6=12, T__5=13, T__4=14, T__3=15, T__2=16, T__1=17, T__0=18, FUNCTION=19, FOREACH=20, WHILE=21, IF=22, ELSE=23, DO=24, END=25, BOOLEAN=26, SKIP=27, VAR=28, IN=29, ASSIGN=30, AND=31, OR=32, - NOT=33, NULL=34, LEFT_PARENTHESIS=35, RIGHT_PARENTHESIS=36, ID=37, COMMENT=38, - MULTILINE_COMMENT=39, ATTRIBUTE_ID=40, STRING=41, NUMBER=42, WHITESPACE=43, - NEWLINE=44; + NOT=33, NULL=34, RETURN=35, LEFT_PARENTHESIS=36, RIGHT_PARENTHESIS=37, + ID=38, COMMENT=39, MULTILINE_COMMENT=40, ATTRIBUTE_ID=41, STRING=42, NUMBER=43, + WHITESPACE=44, NEWLINE=45; public static String[] modeNames = { "DEFAULT_MODE" }; @@ -31,14 +31,14 @@ public class MetaCodeLexer extends Lexer { "';'", "'<='", "'to'", "'>'", "'by'", "'=='", "'/'", "'>='", "'function'", "'foreach'", "'while'", "'if'", "'else'", "'do'", "'end'", "BOOLEAN", "'skip'", "'var'", "'in'", "'='", "'and'", "'or'", "'not'", "'null'", - "'('", "')'", "ID", "COMMENT", "MULTILINE_COMMENT", "ATTRIBUTE_ID", "STRING", - "NUMBER", "WHITESPACE", "NEWLINE" + "'return'", "'('", "')'", "ID", "COMMENT", "MULTILINE_COMMENT", "ATTRIBUTE_ID", + "STRING", "NUMBER", "WHITESPACE", "NEWLINE" }; public static final String[] ruleNames = { "T__17", "T__16", "T__15", "T__14", "T__13", "T__12", "T__11", "T__10", "T__9", "T__8", "T__7", "T__6", "T__5", "T__4", "T__3", "T__2", "T__1", "T__0", "FUNCTION", "FOREACH", "WHILE", "IF", "ELSE", "DO", "END", "BOOLEAN", - "SKIP", "VAR", "IN", "ASSIGN", "AND", "OR", "NOT", "NULL", "LEFT_PARENTHESIS", + "SKIP", "VAR", "IN", "ASSIGN", "AND", "OR", "NOT", "NULL", "RETURN", "LEFT_PARENTHESIS", "RIGHT_PARENTHESIS", "ID", "COMMENT", "MULTILINE_COMMENT", "ATTRIBUTE_ID", "LETTER", "STRING", "NUMBER", "INT", "FLOAT", "DIGIT", "WHITESPACE", "NEWLINE" }; @@ -58,126 +58,160 @@ public MetaCodeLexer(CharStream input) { @Override public String[] getRuleNames() { return ruleNames; } - @Override - public String getSerializedATN() { return _serializedATN; } - @Override public String[] getModeNames() { return modeNames; } @Override public ATN getATN() { return _ATN; } + @Override + public void action(RuleContext _localctx, int ruleIndex, int actionIndex) { + switch (ruleIndex) { + case 38: COMMENT_action((RuleContext)_localctx, actionIndex); break; + + case 39: MULTILINE_COMMENT_action((RuleContext)_localctx, actionIndex); break; + + case 47: WHITESPACE_action((RuleContext)_localctx, actionIndex); break; + + case 48: NEWLINE_action((RuleContext)_localctx, actionIndex); break; + } + } + private void WHITESPACE_action(RuleContext _localctx, int actionIndex) { + switch (actionIndex) { + case 2: skip(); break; + } + } + private void MULTILINE_COMMENT_action(RuleContext _localctx, int actionIndex) { + switch (actionIndex) { + case 1: skip(); break; + } + } + private void NEWLINE_action(RuleContext _localctx, int actionIndex) { + switch (actionIndex) { + case 3: skip(); break; + } + } + private void COMMENT_action(RuleContext _localctx, int actionIndex) { + switch (actionIndex) { + case 0: skip(); break; + } + } + public static final String _serializedATN = - "\3\u0430\ud6d1\u8206\uad2d\u4417\uaef1\u8d80\uaadd\2.\u014a\b\1\4\2\t"+ + "\3\uacf5\uee8c\u4f5d\u8b0d\u4a45\u78bd\u1b2f\u3378\2/\u0153\b\1\4\2\t"+ "\2\4\3\t\3\4\4\t\4\4\5\t\5\4\6\t\6\4\7\t\7\4\b\t\b\4\t\t\t\4\n\t\n\4\13"+ "\t\13\4\f\t\f\4\r\t\r\4\16\t\16\4\17\t\17\4\20\t\20\4\21\t\21\4\22\t\22"+ "\4\23\t\23\4\24\t\24\4\25\t\25\4\26\t\26\4\27\t\27\4\30\t\30\4\31\t\31"+ "\4\32\t\32\4\33\t\33\4\34\t\34\4\35\t\35\4\36\t\36\4\37\t\37\4 \t \4!"+ "\t!\4\"\t\"\4#\t#\4$\t$\4%\t%\4&\t&\4\'\t\'\4(\t(\4)\t)\4*\t*\4+\t+\4"+ - ",\t,\4-\t-\4.\t.\4/\t/\4\60\t\60\4\61\t\61\3\2\3\2\3\3\3\3\3\4\3\4\3\5"+ - "\3\5\3\6\3\6\3\7\3\7\3\b\3\b\3\t\3\t\3\n\3\n\3\13\3\13\3\13\3\f\3\f\3"+ - "\r\3\r\3\r\3\16\3\16\3\16\3\17\3\17\3\20\3\20\3\20\3\21\3\21\3\21\3\22"+ - "\3\22\3\23\3\23\3\23\3\24\3\24\3\24\3\24\3\24\3\24\3\24\3\24\3\24\3\25"+ - "\3\25\3\25\3\25\3\25\3\25\3\25\3\25\3\26\3\26\3\26\3\26\3\26\3\26\3\27"+ - "\3\27\3\27\3\30\3\30\3\30\3\30\3\30\3\31\3\31\3\31\3\32\3\32\3\32\3\32"+ - "\3\33\3\33\3\33\3\33\3\33\3\33\3\33\3\33\3\33\5\33\u00bd\n\33\3\34\3\34"+ - "\3\34\3\34\3\34\3\35\3\35\3\35\3\35\3\36\3\36\3\36\3\37\3\37\3 \3 \3 "+ - "\3 \3!\3!\3!\3\"\3\"\3\"\3\"\3#\3#\3#\3#\3#\3$\3$\3%\3%\3&\3&\5&\u00e3"+ - "\n&\3&\3&\7&\u00e7\n&\f&\16&\u00ea\13&\3\'\3\'\3\'\3\'\7\'\u00f0\n\'\f"+ - "\'\16\'\u00f3\13\'\3\'\3\'\3\'\3\'\3(\3(\3(\3(\7(\u00fd\n(\f(\16(\u0100"+ - "\13(\3(\3(\3(\3(\3(\3)\3)\3)\5)\u010a\n)\3)\3)\7)\u010e\n)\f)\16)\u0111"+ - "\13)\3*\3*\3+\3+\7+\u0117\n+\f+\16+\u011a\13+\3+\3+\3,\3,\5,\u0120\n,"+ - "\3-\6-\u0123\n-\r-\16-\u0124\3.\6.\u0128\n.\r.\16.\u0129\3.\3.\7.\u012e"+ - "\n.\f.\16.\u0131\13.\3.\3.\6.\u0135\n.\r.\16.\u0136\5.\u0139\n.\3/\3/"+ - "\3\60\6\60\u013e\n\60\r\60\16\60\u013f\3\60\3\60\3\61\5\61\u0145\n\61"+ - "\3\61\3\61\3\61\3\61\5\u00f1\u00fe\u0118\2\62\3\3\5\4\7\5\t\6\13\7\r\b"+ - "\17\t\21\n\23\13\25\f\27\r\31\16\33\17\35\20\37\21!\22#\23%\24\'\25)\26"+ - "+\27-\30/\31\61\32\63\33\65\34\67\359\36;\37= ?!A\"C#E$G%I&K\'M(O)Q*S"+ - "\2U+W,Y\2[\2]\2_-a.\3\2\7\4\2\62;aa\5\2//\62;aa\4\2C\\c|\3\2\62;\4\2\13"+ - "\13\"\"\u0157\2\3\3\2\2\2\2\5\3\2\2\2\2\7\3\2\2\2\2\t\3\2\2\2\2\13\3\2"+ - "\2\2\2\r\3\2\2\2\2\17\3\2\2\2\2\21\3\2\2\2\2\23\3\2\2\2\2\25\3\2\2\2\2"+ - "\27\3\2\2\2\2\31\3\2\2\2\2\33\3\2\2\2\2\35\3\2\2\2\2\37\3\2\2\2\2!\3\2"+ - "\2\2\2#\3\2\2\2\2%\3\2\2\2\2\'\3\2\2\2\2)\3\2\2\2\2+\3\2\2\2\2-\3\2\2"+ - "\2\2/\3\2\2\2\2\61\3\2\2\2\2\63\3\2\2\2\2\65\3\2\2\2\2\67\3\2\2\2\29\3"+ - "\2\2\2\2;\3\2\2\2\2=\3\2\2\2\2?\3\2\2\2\2A\3\2\2\2\2C\3\2\2\2\2E\3\2\2"+ - "\2\2G\3\2\2\2\2I\3\2\2\2\2K\3\2\2\2\2M\3\2\2\2\2O\3\2\2\2\2Q\3\2\2\2\2"+ - "U\3\2\2\2\2W\3\2\2\2\2_\3\2\2\2\2a\3\2\2\2\3c\3\2\2\2\5e\3\2\2\2\7g\3"+ - "\2\2\2\ti\3\2\2\2\13k\3\2\2\2\rm\3\2\2\2\17o\3\2\2\2\21q\3\2\2\2\23s\3"+ - "\2\2\2\25u\3\2\2\2\27x\3\2\2\2\31z\3\2\2\2\33}\3\2\2\2\35\u0080\3\2\2"+ - "\2\37\u0082\3\2\2\2!\u0085\3\2\2\2#\u0088\3\2\2\2%\u008a\3\2\2\2\'\u008d"+ - "\3\2\2\2)\u0096\3\2\2\2+\u009e\3\2\2\2-\u00a4\3\2\2\2/\u00a7\3\2\2\2\61"+ - "\u00ac\3\2\2\2\63\u00af\3\2\2\2\65\u00bc\3\2\2\2\67\u00be\3\2\2\29\u00c3"+ - "\3\2\2\2;\u00c7\3\2\2\2=\u00ca\3\2\2\2?\u00cc\3\2\2\2A\u00d0\3\2\2\2C"+ - "\u00d3\3\2\2\2E\u00d7\3\2\2\2G\u00dc\3\2\2\2I\u00de\3\2\2\2K\u00e2\3\2"+ - "\2\2M\u00eb\3\2\2\2O\u00f8\3\2\2\2Q\u0106\3\2\2\2S\u0112\3\2\2\2U\u0114"+ - "\3\2\2\2W\u011f\3\2\2\2Y\u0122\3\2\2\2[\u0138\3\2\2\2]\u013a\3\2\2\2_"+ - "\u013d\3\2\2\2a\u0144\3\2\2\2cd\7_\2\2d\4\3\2\2\2ef\7\60\2\2f\6\3\2\2"+ - "\2gh\7.\2\2h\b\3\2\2\2ij\7-\2\2j\n\3\2\2\2kl\7,\2\2l\f\3\2\2\2mn\7/\2"+ - "\2n\16\3\2\2\2op\7]\2\2p\20\3\2\2\2qr\7<\2\2r\22\3\2\2\2st\7>\2\2t\24"+ - "\3\2\2\2uv\7#\2\2vw\7?\2\2w\26\3\2\2\2xy\7=\2\2y\30\3\2\2\2z{\7>\2\2{"+ - "|\7?\2\2|\32\3\2\2\2}~\7v\2\2~\177\7q\2\2\177\34\3\2\2\2\u0080\u0081\7"+ - "@\2\2\u0081\36\3\2\2\2\u0082\u0083\7d\2\2\u0083\u0084\7{\2\2\u0084 \3"+ - "\2\2\2\u0085\u0086\7?\2\2\u0086\u0087\7?\2\2\u0087\"\3\2\2\2\u0088\u0089"+ - "\7\61\2\2\u0089$\3\2\2\2\u008a\u008b\7@\2\2\u008b\u008c\7?\2\2\u008c&"+ - "\3\2\2\2\u008d\u008e\7h\2\2\u008e\u008f\7w\2\2\u008f\u0090\7p\2\2\u0090"+ - "\u0091\7e\2\2\u0091\u0092\7v\2\2\u0092\u0093\7k\2\2\u0093\u0094\7q\2\2"+ - "\u0094\u0095\7p\2\2\u0095(\3\2\2\2\u0096\u0097\7h\2\2\u0097\u0098\7q\2"+ - "\2\u0098\u0099\7t\2\2\u0099\u009a\7g\2\2\u009a\u009b\7c\2\2\u009b\u009c"+ - "\7e\2\2\u009c\u009d\7j\2\2\u009d*\3\2\2\2\u009e\u009f\7y\2\2\u009f\u00a0"+ - "\7j\2\2\u00a0\u00a1\7k\2\2\u00a1\u00a2\7n\2\2\u00a2\u00a3\7g\2\2\u00a3"+ - ",\3\2\2\2\u00a4\u00a5\7k\2\2\u00a5\u00a6\7h\2\2\u00a6.\3\2\2\2\u00a7\u00a8"+ - "\7g\2\2\u00a8\u00a9\7n\2\2\u00a9\u00aa\7u\2\2\u00aa\u00ab\7g\2\2\u00ab"+ - "\60\3\2\2\2\u00ac\u00ad\7f\2\2\u00ad\u00ae\7q\2\2\u00ae\62\3\2\2\2\u00af"+ - "\u00b0\7g\2\2\u00b0\u00b1\7p\2\2\u00b1\u00b2\7f\2\2\u00b2\64\3\2\2\2\u00b3"+ - "\u00b4\7h\2\2\u00b4\u00b5\7c\2\2\u00b5\u00b6\7n\2\2\u00b6\u00b7\7u\2\2"+ - "\u00b7\u00bd\7g\2\2\u00b8\u00b9\7v\2\2\u00b9\u00ba\7t\2\2\u00ba\u00bb"+ - "\7w\2\2\u00bb\u00bd\7g\2\2\u00bc\u00b3\3\2\2\2\u00bc\u00b8\3\2\2\2\u00bd"+ - "\66\3\2\2\2\u00be\u00bf\7u\2\2\u00bf\u00c0\7m\2\2\u00c0\u00c1\7k\2\2\u00c1"+ - "\u00c2\7r\2\2\u00c28\3\2\2\2\u00c3\u00c4\7x\2\2\u00c4\u00c5\7c\2\2\u00c5"+ - "\u00c6\7t\2\2\u00c6:\3\2\2\2\u00c7\u00c8\7k\2\2\u00c8\u00c9\7p\2\2\u00c9"+ - "<\3\2\2\2\u00ca\u00cb\7?\2\2\u00cb>\3\2\2\2\u00cc\u00cd\7c\2\2\u00cd\u00ce"+ - "\7p\2\2\u00ce\u00cf\7f\2\2\u00cf@\3\2\2\2\u00d0\u00d1\7q\2\2\u00d1\u00d2"+ - "\7t\2\2\u00d2B\3\2\2\2\u00d3\u00d4\7p\2\2\u00d4\u00d5\7q\2\2\u00d5\u00d6"+ - "\7v\2\2\u00d6D\3\2\2\2\u00d7\u00d8\7p\2\2\u00d8\u00d9\7w\2\2\u00d9\u00da"+ - "\7n\2\2\u00da\u00db\7n\2\2\u00dbF\3\2\2\2\u00dc\u00dd\7*\2\2\u00ddH\3"+ - "\2\2\2\u00de\u00df\7+\2\2\u00dfJ\3\2\2\2\u00e0\u00e3\5S*\2\u00e1\u00e3"+ - "\7a\2\2\u00e2\u00e0\3\2\2\2\u00e2\u00e1\3\2\2\2\u00e3\u00e8\3\2\2\2\u00e4"+ - "\u00e7\5S*\2\u00e5\u00e7\t\2\2\2\u00e6\u00e4\3\2\2\2\u00e6\u00e5\3\2\2"+ - "\2\u00e7\u00ea\3\2\2\2\u00e8\u00e6\3\2\2\2\u00e8\u00e9\3\2\2\2\u00e9L"+ - "\3\2\2\2\u00ea\u00e8\3\2\2\2\u00eb\u00ec\7\61\2\2\u00ec\u00ed\7\61\2\2"+ - "\u00ed\u00f1\3\2\2\2\u00ee\u00f0\13\2\2\2\u00ef\u00ee\3\2\2\2\u00f0\u00f3"+ - "\3\2\2\2\u00f1\u00f2\3\2\2\2\u00f1\u00ef\3\2\2\2\u00f2\u00f4\3\2\2\2\u00f3"+ - "\u00f1\3\2\2\2\u00f4\u00f5\5a\61\2\u00f5\u00f6\3\2\2\2\u00f6\u00f7\b\'"+ - "\2\2\u00f7N\3\2\2\2\u00f8\u00f9\7\61\2\2\u00f9\u00fa\7,\2\2\u00fa\u00fe"+ - "\3\2\2\2\u00fb\u00fd\13\2\2\2\u00fc\u00fb\3\2\2\2\u00fd\u0100\3\2\2\2"+ - "\u00fe\u00ff\3\2\2\2\u00fe\u00fc\3\2\2\2\u00ff\u0101\3\2\2\2\u0100\u00fe"+ - "\3\2\2\2\u0101\u0102\7,\2\2\u0102\u0103\7\61\2\2\u0103\u0104\3\2\2\2\u0104"+ - "\u0105\b(\2\2\u0105P\3\2\2\2\u0106\u0109\7B\2\2\u0107\u010a\5S*\2\u0108"+ - "\u010a\7a\2\2\u0109\u0107\3\2\2\2\u0109\u0108\3\2\2\2\u010a\u010f\3\2"+ - "\2\2\u010b\u010e\5S*\2\u010c\u010e\t\3\2\2\u010d\u010b\3\2\2\2\u010d\u010c"+ - "\3\2\2\2\u010e\u0111\3\2\2\2\u010f\u010d\3\2\2\2\u010f\u0110\3\2\2\2\u0110"+ - "R\3\2\2\2\u0111\u010f\3\2\2\2\u0112\u0113\t\4\2\2\u0113T\3\2\2\2\u0114"+ - "\u0118\7$\2\2\u0115\u0117\13\2\2\2\u0116\u0115\3\2\2\2\u0117\u011a\3\2"+ - "\2\2\u0118\u0119\3\2\2\2\u0118\u0116\3\2\2\2\u0119\u011b\3\2\2\2\u011a"+ - "\u0118\3\2\2\2\u011b\u011c\7$\2\2\u011cV\3\2\2\2\u011d\u0120\5Y-\2\u011e"+ - "\u0120\5[.\2\u011f\u011d\3\2\2\2\u011f\u011e\3\2\2\2\u0120X\3\2\2\2\u0121"+ - "\u0123\5]/\2\u0122\u0121\3\2\2\2\u0123\u0124\3\2\2\2\u0124\u0122\3\2\2"+ - "\2\u0124\u0125\3\2\2\2\u0125Z\3\2\2\2\u0126\u0128\5]/\2\u0127\u0126\3"+ - "\2\2\2\u0128\u0129\3\2\2\2\u0129\u0127\3\2\2\2\u0129\u012a\3\2\2\2\u012a"+ - "\u012b\3\2\2\2\u012b\u012f\7\60\2\2\u012c\u012e\5]/\2\u012d\u012c\3\2"+ - "\2\2\u012e\u0131\3\2\2\2\u012f\u012d\3\2\2\2\u012f\u0130\3\2\2\2\u0130"+ - "\u0139\3\2\2\2\u0131\u012f\3\2\2\2\u0132\u0134\7\60\2\2\u0133\u0135\5"+ - "]/\2\u0134\u0133\3\2\2\2\u0135\u0136\3\2\2\2\u0136\u0134\3\2\2\2\u0136"+ - "\u0137\3\2\2\2\u0137\u0139\3\2\2\2\u0138\u0127\3\2\2\2\u0138\u0132\3\2"+ - "\2\2\u0139\\\3\2\2\2\u013a\u013b\t\5\2\2\u013b^\3\2\2\2\u013c\u013e\t"+ - "\6\2\2\u013d\u013c\3\2\2\2\u013e\u013f\3\2\2\2\u013f\u013d\3\2\2\2\u013f"+ - "\u0140\3\2\2\2\u0140\u0141\3\2\2\2\u0141\u0142\b\60\2\2\u0142`\3\2\2\2"+ - "\u0143\u0145\7\17\2\2\u0144\u0143\3\2\2\2\u0144\u0145\3\2\2\2\u0145\u0146"+ - "\3\2\2\2\u0146\u0147\7\f\2\2\u0147\u0148\3\2\2\2\u0148\u0149\b\61\2\2"+ - "\u0149b\3\2\2\2\25\2\u00bc\u00e2\u00e6\u00e8\u00f1\u00fe\u0109\u010d\u010f"+ - "\u0118\u011f\u0124\u0129\u012f\u0136\u0138\u013f\u0144\3\b\2\2"; + ",\t,\4-\t-\4.\t.\4/\t/\4\60\t\60\4\61\t\61\4\62\t\62\3\2\3\2\3\3\3\3\3"+ + "\4\3\4\3\5\3\5\3\6\3\6\3\7\3\7\3\b\3\b\3\t\3\t\3\n\3\n\3\13\3\13\3\13"+ + "\3\f\3\f\3\r\3\r\3\r\3\16\3\16\3\16\3\17\3\17\3\20\3\20\3\20\3\21\3\21"+ + "\3\21\3\22\3\22\3\23\3\23\3\23\3\24\3\24\3\24\3\24\3\24\3\24\3\24\3\24"+ + "\3\24\3\25\3\25\3\25\3\25\3\25\3\25\3\25\3\25\3\26\3\26\3\26\3\26\3\26"+ + "\3\26\3\27\3\27\3\27\3\30\3\30\3\30\3\30\3\30\3\31\3\31\3\31\3\32\3\32"+ + "\3\32\3\32\3\33\3\33\3\33\3\33\3\33\3\33\3\33\3\33\3\33\5\33\u00bf\n\33"+ + "\3\34\3\34\3\34\3\34\3\34\3\35\3\35\3\35\3\35\3\36\3\36\3\36\3\37\3\37"+ + "\3 \3 \3 \3 \3!\3!\3!\3\"\3\"\3\"\3\"\3#\3#\3#\3#\3#\3$\3$\3$\3$\3$\3"+ + "$\3$\3%\3%\3&\3&\3\'\3\'\5\'\u00ec\n\'\3\'\3\'\7\'\u00f0\n\'\f\'\16\'"+ + "\u00f3\13\'\3(\3(\3(\3(\7(\u00f9\n(\f(\16(\u00fc\13(\3(\3(\3(\3(\3)\3"+ + ")\3)\3)\7)\u0106\n)\f)\16)\u0109\13)\3)\3)\3)\3)\3)\3*\3*\3*\5*\u0113"+ + "\n*\3*\3*\7*\u0117\n*\f*\16*\u011a\13*\3+\3+\3,\3,\7,\u0120\n,\f,\16,"+ + "\u0123\13,\3,\3,\3-\3-\5-\u0129\n-\3.\6.\u012c\n.\r.\16.\u012d\3/\6/\u0131"+ + "\n/\r/\16/\u0132\3/\3/\7/\u0137\n/\f/\16/\u013a\13/\3/\3/\6/\u013e\n/"+ + "\r/\16/\u013f\5/\u0142\n/\3\60\3\60\3\61\6\61\u0147\n\61\r\61\16\61\u0148"+ + "\3\61\3\61\3\62\5\62\u014e\n\62\3\62\3\62\3\62\3\62\5\u00fa\u0107\u0121"+ + "\63\3\3\1\5\4\1\7\5\1\t\6\1\13\7\1\r\b\1\17\t\1\21\n\1\23\13\1\25\f\1"+ + "\27\r\1\31\16\1\33\17\1\35\20\1\37\21\1!\22\1#\23\1%\24\1\'\25\1)\26\1"+ + "+\27\1-\30\1/\31\1\61\32\1\63\33\1\65\34\1\67\35\19\36\1;\37\1= \1?!\1"+ + "A\"\1C#\1E$\1G%\1I&\1K\'\1M(\1O)\2Q*\3S+\1U\2\1W,\1Y-\1[\2\1]\2\1_\2\1"+ + "a.\4c/\5\3\2\7\4\2\62;aa\5\2//\62;aa\4\2C\\c|\3\2\62;\4\2\13\13\"\"\u0160"+ + "\2\3\3\2\2\2\2\5\3\2\2\2\2\7\3\2\2\2\2\t\3\2\2\2\2\13\3\2\2\2\2\r\3\2"+ + "\2\2\2\17\3\2\2\2\2\21\3\2\2\2\2\23\3\2\2\2\2\25\3\2\2\2\2\27\3\2\2\2"+ + "\2\31\3\2\2\2\2\33\3\2\2\2\2\35\3\2\2\2\2\37\3\2\2\2\2!\3\2\2\2\2#\3\2"+ + "\2\2\2%\3\2\2\2\2\'\3\2\2\2\2)\3\2\2\2\2+\3\2\2\2\2-\3\2\2\2\2/\3\2\2"+ + "\2\2\61\3\2\2\2\2\63\3\2\2\2\2\65\3\2\2\2\2\67\3\2\2\2\29\3\2\2\2\2;\3"+ + "\2\2\2\2=\3\2\2\2\2?\3\2\2\2\2A\3\2\2\2\2C\3\2\2\2\2E\3\2\2\2\2G\3\2\2"+ + "\2\2I\3\2\2\2\2K\3\2\2\2\2M\3\2\2\2\2O\3\2\2\2\2Q\3\2\2\2\2S\3\2\2\2\2"+ + "W\3\2\2\2\2Y\3\2\2\2\2a\3\2\2\2\2c\3\2\2\2\3e\3\2\2\2\5g\3\2\2\2\7i\3"+ + "\2\2\2\tk\3\2\2\2\13m\3\2\2\2\ro\3\2\2\2\17q\3\2\2\2\21s\3\2\2\2\23u\3"+ + "\2\2\2\25w\3\2\2\2\27z\3\2\2\2\31|\3\2\2\2\33\177\3\2\2\2\35\u0082\3\2"+ + "\2\2\37\u0084\3\2\2\2!\u0087\3\2\2\2#\u008a\3\2\2\2%\u008c\3\2\2\2\'\u008f"+ + "\3\2\2\2)\u0098\3\2\2\2+\u00a0\3\2\2\2-\u00a6\3\2\2\2/\u00a9\3\2\2\2\61"+ + "\u00ae\3\2\2\2\63\u00b1\3\2\2\2\65\u00be\3\2\2\2\67\u00c0\3\2\2\29\u00c5"+ + "\3\2\2\2;\u00c9\3\2\2\2=\u00cc\3\2\2\2?\u00ce\3\2\2\2A\u00d2\3\2\2\2C"+ + "\u00d5\3\2\2\2E\u00d9\3\2\2\2G\u00de\3\2\2\2I\u00e5\3\2\2\2K\u00e7\3\2"+ + "\2\2M\u00eb\3\2\2\2O\u00f4\3\2\2\2Q\u0101\3\2\2\2S\u010f\3\2\2\2U\u011b"+ + "\3\2\2\2W\u011d\3\2\2\2Y\u0128\3\2\2\2[\u012b\3\2\2\2]\u0141\3\2\2\2_"+ + "\u0143\3\2\2\2a\u0146\3\2\2\2c\u014d\3\2\2\2ef\7_\2\2f\4\3\2\2\2gh\7\60"+ + "\2\2h\6\3\2\2\2ij\7.\2\2j\b\3\2\2\2kl\7-\2\2l\n\3\2\2\2mn\7,\2\2n\f\3"+ + "\2\2\2op\7/\2\2p\16\3\2\2\2qr\7]\2\2r\20\3\2\2\2st\7<\2\2t\22\3\2\2\2"+ + "uv\7>\2\2v\24\3\2\2\2wx\7#\2\2xy\7?\2\2y\26\3\2\2\2z{\7=\2\2{\30\3\2\2"+ + "\2|}\7>\2\2}~\7?\2\2~\32\3\2\2\2\177\u0080\7v\2\2\u0080\u0081\7q\2\2\u0081"+ + "\34\3\2\2\2\u0082\u0083\7@\2\2\u0083\36\3\2\2\2\u0084\u0085\7d\2\2\u0085"+ + "\u0086\7{\2\2\u0086 \3\2\2\2\u0087\u0088\7?\2\2\u0088\u0089\7?\2\2\u0089"+ + "\"\3\2\2\2\u008a\u008b\7\61\2\2\u008b$\3\2\2\2\u008c\u008d\7@\2\2\u008d"+ + "\u008e\7?\2\2\u008e&\3\2\2\2\u008f\u0090\7h\2\2\u0090\u0091\7w\2\2\u0091"+ + "\u0092\7p\2\2\u0092\u0093\7e\2\2\u0093\u0094\7v\2\2\u0094\u0095\7k\2\2"+ + "\u0095\u0096\7q\2\2\u0096\u0097\7p\2\2\u0097(\3\2\2\2\u0098\u0099\7h\2"+ + "\2\u0099\u009a\7q\2\2\u009a\u009b\7t\2\2\u009b\u009c\7g\2\2\u009c\u009d"+ + "\7c\2\2\u009d\u009e\7e\2\2\u009e\u009f\7j\2\2\u009f*\3\2\2\2\u00a0\u00a1"+ + "\7y\2\2\u00a1\u00a2\7j\2\2\u00a2\u00a3\7k\2\2\u00a3\u00a4\7n\2\2\u00a4"+ + "\u00a5\7g\2\2\u00a5,\3\2\2\2\u00a6\u00a7\7k\2\2\u00a7\u00a8\7h\2\2\u00a8"+ + ".\3\2\2\2\u00a9\u00aa\7g\2\2\u00aa\u00ab\7n\2\2\u00ab\u00ac\7u\2\2\u00ac"+ + "\u00ad\7g\2\2\u00ad\60\3\2\2\2\u00ae\u00af\7f\2\2\u00af\u00b0\7q\2\2\u00b0"+ + "\62\3\2\2\2\u00b1\u00b2\7g\2\2\u00b2\u00b3\7p\2\2\u00b3\u00b4\7f\2\2\u00b4"+ + "\64\3\2\2\2\u00b5\u00b6\7h\2\2\u00b6\u00b7\7c\2\2\u00b7\u00b8\7n\2\2\u00b8"+ + "\u00b9\7u\2\2\u00b9\u00bf\7g\2\2\u00ba\u00bb\7v\2\2\u00bb\u00bc\7t\2\2"+ + "\u00bc\u00bd\7w\2\2\u00bd\u00bf\7g\2\2\u00be\u00b5\3\2\2\2\u00be\u00ba"+ + "\3\2\2\2\u00bf\66\3\2\2\2\u00c0\u00c1\7u\2\2\u00c1\u00c2\7m\2\2\u00c2"+ + "\u00c3\7k\2\2\u00c3\u00c4\7r\2\2\u00c48\3\2\2\2\u00c5\u00c6\7x\2\2\u00c6"+ + "\u00c7\7c\2\2\u00c7\u00c8\7t\2\2\u00c8:\3\2\2\2\u00c9\u00ca\7k\2\2\u00ca"+ + "\u00cb\7p\2\2\u00cb<\3\2\2\2\u00cc\u00cd\7?\2\2\u00cd>\3\2\2\2\u00ce\u00cf"+ + "\7c\2\2\u00cf\u00d0\7p\2\2\u00d0\u00d1\7f\2\2\u00d1@\3\2\2\2\u00d2\u00d3"+ + "\7q\2\2\u00d3\u00d4\7t\2\2\u00d4B\3\2\2\2\u00d5\u00d6\7p\2\2\u00d6\u00d7"+ + "\7q\2\2\u00d7\u00d8\7v\2\2\u00d8D\3\2\2\2\u00d9\u00da\7p\2\2\u00da\u00db"+ + "\7w\2\2\u00db\u00dc\7n\2\2\u00dc\u00dd\7n\2\2\u00ddF\3\2\2\2\u00de\u00df"+ + "\7t\2\2\u00df\u00e0\7g\2\2\u00e0\u00e1\7v\2\2\u00e1\u00e2\7w\2\2\u00e2"+ + "\u00e3\7t\2\2\u00e3\u00e4\7p\2\2\u00e4H\3\2\2\2\u00e5\u00e6\7*\2\2\u00e6"+ + "J\3\2\2\2\u00e7\u00e8\7+\2\2\u00e8L\3\2\2\2\u00e9\u00ec\5U+\2\u00ea\u00ec"+ + "\7a\2\2\u00eb\u00e9\3\2\2\2\u00eb\u00ea\3\2\2\2\u00ec\u00f1\3\2\2\2\u00ed"+ + "\u00f0\5U+\2\u00ee\u00f0\t\2\2\2\u00ef\u00ed\3\2\2\2\u00ef\u00ee\3\2\2"+ + "\2\u00f0\u00f3\3\2\2\2\u00f1\u00ef\3\2\2\2\u00f1\u00f2\3\2\2\2\u00f2N"+ + "\3\2\2\2\u00f3\u00f1\3\2\2\2\u00f4\u00f5\7\61\2\2\u00f5\u00f6\7\61\2\2"+ + "\u00f6\u00fa\3\2\2\2\u00f7\u00f9\13\2\2\2\u00f8\u00f7\3\2\2\2\u00f9\u00fc"+ + "\3\2\2\2\u00fa\u00fb\3\2\2\2\u00fa\u00f8\3\2\2\2\u00fb\u00fd\3\2\2\2\u00fc"+ + "\u00fa\3\2\2\2\u00fd\u00fe\5c\62\2\u00fe\u00ff\3\2\2\2\u00ff\u0100\b("+ + "\2\2\u0100P\3\2\2\2\u0101\u0102\7\61\2\2\u0102\u0103\7,\2\2\u0103\u0107"+ + "\3\2\2\2\u0104\u0106\13\2\2\2\u0105\u0104\3\2\2\2\u0106\u0109\3\2\2\2"+ + "\u0107\u0108\3\2\2\2\u0107\u0105\3\2\2\2\u0108\u010a\3\2\2\2\u0109\u0107"+ + "\3\2\2\2\u010a\u010b\7,\2\2\u010b\u010c\7\61\2\2\u010c\u010d\3\2\2\2\u010d"+ + "\u010e\b)\3\2\u010eR\3\2\2\2\u010f\u0112\7B\2\2\u0110\u0113\5U+\2\u0111"+ + "\u0113\7a\2\2\u0112\u0110\3\2\2\2\u0112\u0111\3\2\2\2\u0113\u0118\3\2"+ + "\2\2\u0114\u0117\5U+\2\u0115\u0117\t\3\2\2\u0116\u0114\3\2\2\2\u0116\u0115"+ + "\3\2\2\2\u0117\u011a\3\2\2\2\u0118\u0116\3\2\2\2\u0118\u0119\3\2\2\2\u0119"+ + "T\3\2\2\2\u011a\u0118\3\2\2\2\u011b\u011c\t\4\2\2\u011cV\3\2\2\2\u011d"+ + "\u0121\7$\2\2\u011e\u0120\13\2\2\2\u011f\u011e\3\2\2\2\u0120\u0123\3\2"+ + "\2\2\u0121\u0122\3\2\2\2\u0121\u011f\3\2\2\2\u0122\u0124\3\2\2\2\u0123"+ + "\u0121\3\2\2\2\u0124\u0125\7$\2\2\u0125X\3\2\2\2\u0126\u0129\5[.\2\u0127"+ + "\u0129\5]/\2\u0128\u0126\3\2\2\2\u0128\u0127\3\2\2\2\u0129Z\3\2\2\2\u012a"+ + "\u012c\5_\60\2\u012b\u012a\3\2\2\2\u012c\u012d\3\2\2\2\u012d\u012b\3\2"+ + "\2\2\u012d\u012e\3\2\2\2\u012e\\\3\2\2\2\u012f\u0131\5_\60\2\u0130\u012f"+ + "\3\2\2\2\u0131\u0132\3\2\2\2\u0132\u0130\3\2\2\2\u0132\u0133\3\2\2\2\u0133"+ + "\u0134\3\2\2\2\u0134\u0138\7\60\2\2\u0135\u0137\5_\60\2\u0136\u0135\3"+ + "\2\2\2\u0137\u013a\3\2\2\2\u0138\u0136\3\2\2\2\u0138\u0139\3\2\2\2\u0139"+ + "\u0142\3\2\2\2\u013a\u0138\3\2\2\2\u013b\u013d\7\60\2\2\u013c\u013e\5"+ + "_\60\2\u013d\u013c\3\2\2\2\u013e\u013f\3\2\2\2\u013f\u013d\3\2\2\2\u013f"+ + "\u0140\3\2\2\2\u0140\u0142\3\2\2\2\u0141\u0130\3\2\2\2\u0141\u013b\3\2"+ + "\2\2\u0142^\3\2\2\2\u0143\u0144\t\5\2\2\u0144`\3\2\2\2\u0145\u0147\t\6"+ + "\2\2\u0146\u0145\3\2\2\2\u0147\u0148\3\2\2\2\u0148\u0146\3\2\2\2\u0148"+ + "\u0149\3\2\2\2\u0149\u014a\3\2\2\2\u014a\u014b\b\61\4\2\u014bb\3\2\2\2"+ + "\u014c\u014e\7\17\2\2\u014d\u014c\3\2\2\2\u014d\u014e\3\2\2\2\u014e\u014f"+ + "\3\2\2\2\u014f\u0150\7\f\2\2\u0150\u0151\3\2\2\2\u0151\u0152\b\62\5\2"+ + "\u0152d\3\2\2\2\25\2\u00be\u00eb\u00ef\u00f1\u00fa\u0107\u0112\u0116\u0118"+ + "\u0121\u0128\u012d\u0132\u0138\u013f\u0141\u0148\u014d"; public static final ATN _ATN = - new ATNDeserializer().deserialize(_serializedATN.toCharArray()); + ATNSimulator.deserialize(_serializedATN.toCharArray()); static { _decisionToDFA = new DFA[_ATN.getNumberOfDecisions()]; for (int i = 0; i < _ATN.getNumberOfDecisions(); i++) { diff --git a/grammar/src/MetaCodeLexer.tokens b/grammar/src/MetaCodeLexer.tokens index 7b17149..e763b7a 100644 --- a/grammar/src/MetaCodeLexer.tokens +++ b/grammar/src/MetaCodeLexer.tokens @@ -2,12 +2,12 @@ FUNCTION=19 WHILE=21 NULL=34 ELSE=23 -NUMBER=42 -ATTRIBUTE_ID=40 -WHITESPACE=43 +NUMBER=43 +ATTRIBUTE_ID=41 +WHITESPACE=44 DO=24 NOT=33 -ID=37 +ID=38 AND=31 T__9=9 T__8=10 @@ -17,31 +17,32 @@ T__5=13 IF=22 SKIP=27 T__4=14 -MULTILINE_COMMENT=39 +MULTILINE_COMMENT=40 BOOLEAN=26 T__16=2 IN=29 T__15=3 -NEWLINE=44 -RIGHT_PARENTHESIS=36 +NEWLINE=45 +RIGHT_PARENTHESIS=37 T__17=1 T__12=6 T__11=7 T__14=4 -LEFT_PARENTHESIS=35 +LEFT_PARENTHESIS=36 T__13=5 T__1=17 T__0=18 OR=32 T__10=8 T__3=15 +RETURN=35 ASSIGN=30 T__2=16 VAR=28 FOREACH=20 -COMMENT=38 +COMMENT=39 END=25 -STRING=41 +STRING=42 'foreach'=20 'end'=25 '>='=18 @@ -50,9 +51,10 @@ STRING=41 'null'=34 '>'=14 ';'=11 +'return'=35 '='=30 '+'=4 -')'=36 +')'=37 '.'=2 'function'=19 'do'=24 @@ -66,7 +68,7 @@ STRING=41 '!='=10 '<'=9 'if'=22 -'('=35 +'('=36 'not'=33 ':'=8 'or'=32 diff --git a/grammar/src/MetaCodeListener.class b/grammar/src/MetaCodeListener.class index b659616..9adde38 100644 Binary files a/grammar/src/MetaCodeListener.class and b/grammar/src/MetaCodeListener.class differ diff --git a/grammar/src/MetaCodeListener.java b/grammar/src/MetaCodeListener.java index a47c418..53fd37a 100644 --- a/grammar/src/MetaCodeListener.java +++ b/grammar/src/MetaCodeListener.java @@ -1,4 +1,4 @@ -// Generated from ../MetaCode.g4 by ANTLR 4.2 +// Generated from ../MetaCode.g4 by ANTLR 4.1 import org.antlr.v4.runtime.misc.NotNull; import org.antlr.v4.runtime.tree.ParseTreeListener; @@ -29,6 +29,17 @@ public interface MetaCodeListener extends ParseTreeListener { */ void exitFormalParameter(@NotNull MetaCodeParser.FormalParameterContext ctx); + /** + * Enter a parse tree produced by {@link MetaCodeParser#returnStatement}. + * @param ctx the parse tree + */ + void enterReturnStatement(@NotNull MetaCodeParser.ReturnStatementContext ctx); + /** + * Exit a parse tree produced by {@link MetaCodeParser#returnStatement}. + * @param ctx the parse tree + */ + void exitReturnStatement(@NotNull MetaCodeParser.ReturnStatementContext ctx); + /** * Enter a parse tree produced by {@link MetaCodeParser#attribute}. * @param ctx the parse tree @@ -172,6 +183,17 @@ public interface MetaCodeListener extends ParseTreeListener { */ void exitBooleanConstant(@NotNull MetaCodeParser.BooleanConstantContext ctx); + /** + * Enter a parse tree produced by {@link MetaCodeParser#memberTagExpression}. + * @param ctx the parse tree + */ + void enterMemberTagExpression(@NotNull MetaCodeParser.MemberTagExpressionContext ctx); + /** + * Exit a parse tree produced by {@link MetaCodeParser#memberTagExpression}. + * @param ctx the parse tree + */ + void exitMemberTagExpression(@NotNull MetaCodeParser.MemberTagExpressionContext ctx); + /** * Enter a parse tree produced by {@link MetaCodeParser#assignmentExpression}. * @param ctx the parse tree @@ -305,24 +327,24 @@ public interface MetaCodeListener extends ParseTreeListener { void exitForeachStatement(@NotNull MetaCodeParser.ForeachStatementContext ctx); /** - * Enter a parse tree produced by {@link MetaCodeParser#identifier}. + * Enter a parse tree produced by {@link MetaCodeParser#stringConstant}. * @param ctx the parse tree */ - void enterIdentifier(@NotNull MetaCodeParser.IdentifierContext ctx); + void enterStringConstant(@NotNull MetaCodeParser.StringConstantContext ctx); /** - * Exit a parse tree produced by {@link MetaCodeParser#identifier}. + * Exit a parse tree produced by {@link MetaCodeParser#stringConstant}. * @param ctx the parse tree */ - void exitIdentifier(@NotNull MetaCodeParser.IdentifierContext ctx); + void exitStringConstant(@NotNull MetaCodeParser.StringConstantContext ctx); /** - * Enter a parse tree produced by {@link MetaCodeParser#stringConstant}. + * Enter a parse tree produced by {@link MetaCodeParser#identifier}. * @param ctx the parse tree */ - void enterStringConstant(@NotNull MetaCodeParser.StringConstantContext ctx); + void enterIdentifier(@NotNull MetaCodeParser.IdentifierContext ctx); /** - * Exit a parse tree produced by {@link MetaCodeParser#stringConstant}. + * Exit a parse tree produced by {@link MetaCodeParser#identifier}. * @param ctx the parse tree */ - void exitStringConstant(@NotNull MetaCodeParser.StringConstantContext ctx); + void exitIdentifier(@NotNull MetaCodeParser.IdentifierContext ctx); } \ No newline at end of file diff --git a/grammar/src/MetaCodeParser$ActualParameterListContext.class b/grammar/src/MetaCodeParser$ActualParameterListContext.class index 0310b4b..473950a 100644 Binary files a/grammar/src/MetaCodeParser$ActualParameterListContext.class and b/grammar/src/MetaCodeParser$ActualParameterListContext.class differ diff --git a/grammar/src/MetaCodeParser$ArrayConstantContext.class b/grammar/src/MetaCodeParser$ArrayConstantContext.class index 65ae2b3..9b9d152 100644 Binary files a/grammar/src/MetaCodeParser$ArrayConstantContext.class and b/grammar/src/MetaCodeParser$ArrayConstantContext.class differ diff --git a/grammar/src/MetaCodeParser$AssignmentExpressionContext.class b/grammar/src/MetaCodeParser$AssignmentExpressionContext.class index 5270337..35f47cf 100644 Binary files a/grammar/src/MetaCodeParser$AssignmentExpressionContext.class and b/grammar/src/MetaCodeParser$AssignmentExpressionContext.class differ diff --git a/grammar/src/MetaCodeParser$AttributeContext.class b/grammar/src/MetaCodeParser$AttributeContext.class index 20bf382..e53cbb2 100644 Binary files a/grammar/src/MetaCodeParser$AttributeContext.class and b/grammar/src/MetaCodeParser$AttributeContext.class differ diff --git a/grammar/src/MetaCodeParser$AttributesContext.class b/grammar/src/MetaCodeParser$AttributesContext.class index cadd7ba..c41f67d 100644 Binary files a/grammar/src/MetaCodeParser$AttributesContext.class and b/grammar/src/MetaCodeParser$AttributesContext.class differ diff --git a/grammar/src/MetaCodeParser$BlockStatementContext.class b/grammar/src/MetaCodeParser$BlockStatementContext.class index b81a47f..6d1e9b9 100644 Binary files a/grammar/src/MetaCodeParser$BlockStatementContext.class and b/grammar/src/MetaCodeParser$BlockStatementContext.class differ diff --git a/grammar/src/MetaCodeParser$BooleanConstantContext.class b/grammar/src/MetaCodeParser$BooleanConstantContext.class index d763633..6d1f1c8 100644 Binary files a/grammar/src/MetaCodeParser$BooleanConstantContext.class and b/grammar/src/MetaCodeParser$BooleanConstantContext.class differ diff --git a/grammar/src/MetaCodeParser$ConstantContext.class b/grammar/src/MetaCodeParser$ConstantContext.class index 0fc9787..ad03d7d 100644 Binary files a/grammar/src/MetaCodeParser$ConstantContext.class and b/grammar/src/MetaCodeParser$ConstantContext.class differ diff --git a/grammar/src/MetaCodeParser$ElseIfStatementContext.class b/grammar/src/MetaCodeParser$ElseIfStatementContext.class index dd42e50..f2776d2 100644 Binary files a/grammar/src/MetaCodeParser$ElseIfStatementContext.class and b/grammar/src/MetaCodeParser$ElseIfStatementContext.class differ diff --git a/grammar/src/MetaCodeParser$ExpressionContext.class b/grammar/src/MetaCodeParser$ExpressionContext.class index af0b2f2..60d4901 100644 Binary files a/grammar/src/MetaCodeParser$ExpressionContext.class and b/grammar/src/MetaCodeParser$ExpressionContext.class differ diff --git a/grammar/src/MetaCodeParser$ForeachStatementContext.class b/grammar/src/MetaCodeParser$ForeachStatementContext.class index 9646d71..187f9ce 100644 Binary files a/grammar/src/MetaCodeParser$ForeachStatementContext.class and b/grammar/src/MetaCodeParser$ForeachStatementContext.class differ diff --git a/grammar/src/MetaCodeParser$FormalParameterContext.class b/grammar/src/MetaCodeParser$FormalParameterContext.class index 3894d08..d37b2ff 100644 Binary files a/grammar/src/MetaCodeParser$FormalParameterContext.class and b/grammar/src/MetaCodeParser$FormalParameterContext.class differ diff --git a/grammar/src/MetaCodeParser$FormalParameterListContext.class b/grammar/src/MetaCodeParser$FormalParameterListContext.class index db10e0b..657d9a2 100644 Binary files a/grammar/src/MetaCodeParser$FormalParameterListContext.class and b/grammar/src/MetaCodeParser$FormalParameterListContext.class differ diff --git a/grammar/src/MetaCodeParser$FunctionCallExpressionContext.class b/grammar/src/MetaCodeParser$FunctionCallExpressionContext.class index 17e7b42..9137914 100644 Binary files a/grammar/src/MetaCodeParser$FunctionCallExpressionContext.class and b/grammar/src/MetaCodeParser$FunctionCallExpressionContext.class differ diff --git a/grammar/src/MetaCodeParser$FunctionExpressionContext.class b/grammar/src/MetaCodeParser$FunctionExpressionContext.class index 0211ad5..6771e9b 100644 Binary files a/grammar/src/MetaCodeParser$FunctionExpressionContext.class and b/grammar/src/MetaCodeParser$FunctionExpressionContext.class differ diff --git a/grammar/src/MetaCodeParser$IdentifierContext.class b/grammar/src/MetaCodeParser$IdentifierContext.class index 3727e4c..70c614f 100644 Binary files a/grammar/src/MetaCodeParser$IdentifierContext.class and b/grammar/src/MetaCodeParser$IdentifierContext.class differ diff --git a/grammar/src/MetaCodeParser$IfStatementContext.class b/grammar/src/MetaCodeParser$IfStatementContext.class index 75786e2..2fe22d3 100644 Binary files a/grammar/src/MetaCodeParser$IfStatementContext.class and b/grammar/src/MetaCodeParser$IfStatementContext.class differ diff --git a/grammar/src/MetaCodeParser$InitContext.class b/grammar/src/MetaCodeParser$InitContext.class index 44c141a..cc3e06a 100644 Binary files a/grammar/src/MetaCodeParser$InitContext.class and b/grammar/src/MetaCodeParser$InitContext.class differ diff --git a/grammar/src/MetaCodeParser$IntervalConstantContext.class b/grammar/src/MetaCodeParser$IntervalConstantContext.class index 229ce6e..a1805f2 100644 Binary files a/grammar/src/MetaCodeParser$IntervalConstantContext.class and b/grammar/src/MetaCodeParser$IntervalConstantContext.class differ diff --git a/grammar/src/MetaCodeParser$MemberExpressionContext.class b/grammar/src/MetaCodeParser$MemberExpressionContext.class index 6e20715..2c31e96 100644 Binary files a/grammar/src/MetaCodeParser$MemberExpressionContext.class and b/grammar/src/MetaCodeParser$MemberExpressionContext.class differ diff --git a/grammar/src/MetaCodeParser$MemberTagExpressionContext.class b/grammar/src/MetaCodeParser$MemberTagExpressionContext.class new file mode 100644 index 0000000..6257117 Binary files /dev/null and b/grammar/src/MetaCodeParser$MemberTagExpressionContext.class differ diff --git a/grammar/src/MetaCodeParser$NumberConstantContext.class b/grammar/src/MetaCodeParser$NumberConstantContext.class index abea3dd..67707a4 100644 Binary files a/grammar/src/MetaCodeParser$NumberConstantContext.class and b/grammar/src/MetaCodeParser$NumberConstantContext.class differ diff --git a/grammar/src/MetaCodeParser$PrimaryExpressionContext.class b/grammar/src/MetaCodeParser$PrimaryExpressionContext.class index 7de5194..820b269 100644 Binary files a/grammar/src/MetaCodeParser$PrimaryExpressionContext.class and b/grammar/src/MetaCodeParser$PrimaryExpressionContext.class differ diff --git a/grammar/src/MetaCodeParser$ReturnStatementContext.class b/grammar/src/MetaCodeParser$ReturnStatementContext.class new file mode 100644 index 0000000..373e85c Binary files /dev/null and b/grammar/src/MetaCodeParser$ReturnStatementContext.class differ diff --git a/grammar/src/MetaCodeParser$SkipStatementContext.class b/grammar/src/MetaCodeParser$SkipStatementContext.class index 7b575c5..9a8632b 100644 Binary files a/grammar/src/MetaCodeParser$SkipStatementContext.class and b/grammar/src/MetaCodeParser$SkipStatementContext.class differ diff --git a/grammar/src/MetaCodeParser$StatementContext.class b/grammar/src/MetaCodeParser$StatementContext.class index a7e3de0..eb6ccad 100644 Binary files a/grammar/src/MetaCodeParser$StatementContext.class and b/grammar/src/MetaCodeParser$StatementContext.class differ diff --git a/grammar/src/MetaCodeParser$StatementsContext.class b/grammar/src/MetaCodeParser$StatementsContext.class index 9e14d60..1e083ff 100644 Binary files a/grammar/src/MetaCodeParser$StatementsContext.class and b/grammar/src/MetaCodeParser$StatementsContext.class differ diff --git a/grammar/src/MetaCodeParser$StringConstantContext.class b/grammar/src/MetaCodeParser$StringConstantContext.class index 34427b8..4321530 100644 Binary files a/grammar/src/MetaCodeParser$StringConstantContext.class and b/grammar/src/MetaCodeParser$StringConstantContext.class differ diff --git a/grammar/src/MetaCodeParser$TypeNameContext.class b/grammar/src/MetaCodeParser$TypeNameContext.class index c4f854b..c30e733 100644 Binary files a/grammar/src/MetaCodeParser$TypeNameContext.class and b/grammar/src/MetaCodeParser$TypeNameContext.class differ diff --git a/grammar/src/MetaCodeParser$VariableDeclarationContext.class b/grammar/src/MetaCodeParser$VariableDeclarationContext.class index dcc49b8..b3ab5ba 100644 Binary files a/grammar/src/MetaCodeParser$VariableDeclarationContext.class and b/grammar/src/MetaCodeParser$VariableDeclarationContext.class differ diff --git a/grammar/src/MetaCodeParser$WhileStatementContext.class b/grammar/src/MetaCodeParser$WhileStatementContext.class index 014b5ce..99a94a4 100644 Binary files a/grammar/src/MetaCodeParser$WhileStatementContext.class and b/grammar/src/MetaCodeParser$WhileStatementContext.class differ diff --git a/grammar/src/MetaCodeParser.class b/grammar/src/MetaCodeParser.class index 297eb2b..33076ca 100644 Binary files a/grammar/src/MetaCodeParser.class and b/grammar/src/MetaCodeParser.class differ diff --git a/grammar/src/MetaCodeParser.java b/grammar/src/MetaCodeParser.java index f91bc77..1b6891e 100644 --- a/grammar/src/MetaCodeParser.java +++ b/grammar/src/MetaCodeParser.java @@ -1,4 +1,4 @@ -// Generated from ../MetaCode.g4 by ANTLR 4.2 +// Generated from ../MetaCode.g4 by ANTLR 4.1 import org.antlr.v4.runtime.atn.*; import org.antlr.v4.runtime.dfa.DFA; import org.antlr.v4.runtime.*; @@ -18,35 +18,37 @@ public class MetaCodeParser extends Parser { T__9=9, T__8=10, T__7=11, T__6=12, T__5=13, T__4=14, T__3=15, T__2=16, T__1=17, T__0=18, FUNCTION=19, FOREACH=20, WHILE=21, IF=22, ELSE=23, DO=24, END=25, BOOLEAN=26, SKIP=27, VAR=28, IN=29, ASSIGN=30, AND=31, OR=32, - NOT=33, NULL=34, LEFT_PARENTHESIS=35, RIGHT_PARENTHESIS=36, ID=37, COMMENT=38, - MULTILINE_COMMENT=39, ATTRIBUTE_ID=40, STRING=41, NUMBER=42, WHITESPACE=43, - NEWLINE=44; + NOT=33, NULL=34, RETURN=35, LEFT_PARENTHESIS=36, RIGHT_PARENTHESIS=37, + ID=38, COMMENT=39, MULTILINE_COMMENT=40, ATTRIBUTE_ID=41, STRING=42, NUMBER=43, + WHITESPACE=44, NEWLINE=45; public static final String[] tokenNames = { "", "']'", "'.'", "','", "'+'", "'*'", "'-'", "'['", "':'", "'<'", "'!='", "';'", "'<='", "'to'", "'>'", "'by'", "'=='", "'/'", "'>='", "'function'", "'foreach'", "'while'", "'if'", "'else'", "'do'", "'end'", "BOOLEAN", "'skip'", "'var'", "'in'", "'='", "'and'", "'or'", "'not'", "'null'", - "'('", "')'", "ID", "COMMENT", "MULTILINE_COMMENT", "ATTRIBUTE_ID", "STRING", - "NUMBER", "WHITESPACE", "NEWLINE" + "'return'", "'('", "')'", "ID", "COMMENT", "MULTILINE_COMMENT", "ATTRIBUTE_ID", + "STRING", "NUMBER", "WHITESPACE", "NEWLINE" }; public static final int RULE_init = 0, RULE_statements = 1, RULE_statement = 2, RULE_variableDeclaration = 3, RULE_expression = 4, RULE_functionCallExpression = 5, RULE_memberExpression = 6, - RULE_primaryExpression = 7, RULE_functionExpression = 8, RULE_foreachStatement = 9, - RULE_whileStatement = 10, RULE_blockStatement = 11, RULE_skipStatement = 12, - RULE_assignmentExpression = 13, RULE_ifStatement = 14, RULE_elseIfStatement = 15, - RULE_formalParameterList = 16, RULE_formalParameter = 17, RULE_actualParameterList = 18, - RULE_typeName = 19, RULE_constant = 20, RULE_identifier = 21, RULE_numberConstant = 22, - RULE_stringConstant = 23, RULE_booleanConstant = 24, RULE_arrayConstant = 25, - RULE_intervalConstant = 26, RULE_attributes = 27, RULE_attribute = 28; + RULE_memberTagExpression = 7, RULE_primaryExpression = 8, RULE_functionExpression = 9, + RULE_foreachStatement = 10, RULE_whileStatement = 11, RULE_blockStatement = 12, + RULE_skipStatement = 13, RULE_returnStatement = 14, RULE_assignmentExpression = 15, + RULE_ifStatement = 16, RULE_elseIfStatement = 17, RULE_formalParameterList = 18, + RULE_formalParameter = 19, RULE_actualParameterList = 20, RULE_typeName = 21, + RULE_constant = 22, RULE_identifier = 23, RULE_numberConstant = 24, RULE_stringConstant = 25, + RULE_booleanConstant = 26, RULE_arrayConstant = 27, RULE_intervalConstant = 28, + RULE_attributes = 29, RULE_attribute = 30; public static final String[] ruleNames = { "init", "statements", "statement", "variableDeclaration", "expression", - "functionCallExpression", "memberExpression", "primaryExpression", "functionExpression", - "foreachStatement", "whileStatement", "blockStatement", "skipStatement", - "assignmentExpression", "ifStatement", "elseIfStatement", "formalParameterList", - "formalParameter", "actualParameterList", "typeName", "constant", "identifier", - "numberConstant", "stringConstant", "booleanConstant", "arrayConstant", - "intervalConstant", "attributes", "attribute" + "functionCallExpression", "memberExpression", "memberTagExpression", "primaryExpression", + "functionExpression", "foreachStatement", "whileStatement", "blockStatement", + "skipStatement", "returnStatement", "assignmentExpression", "ifStatement", + "elseIfStatement", "formalParameterList", "formalParameter", "actualParameterList", + "typeName", "constant", "identifier", "numberConstant", "stringConstant", + "booleanConstant", "arrayConstant", "intervalConstant", "attributes", + "attribute" }; @Override @@ -58,9 +60,6 @@ public class MetaCodeParser extends Parser { @Override public String[] getRuleNames() { return ruleNames; } - @Override - public String getSerializedATN() { return _serializedATN; } - @Override public ATN getATN() { return _ATN; } @@ -97,7 +96,7 @@ public final InitContext init() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(58); statements(); + setState(62); statements(); } } catch (RecognitionException re) { @@ -144,20 +143,20 @@ public final StatementsContext statements() throws RecognitionException { try { enterOuterAlt(_localctx, 1); { - setState(63); + setState(67); _errHandler.sync(this); _la = _input.LA(1); do { { { - setState(60); statement(); - setState(61); match(11); + setState(64); statement(); + setState(65); match(11); } } - setState(65); + setState(69); _errHandler.sync(this); _la = _input.LA(1); - } while ( (((_la) & ~0x3f) == 0 && ((1L << _la) & ((1L << 7) | (1L << FUNCTION) | (1L << FOREACH) | (1L << WHILE) | (1L << IF) | (1L << DO) | (1L << BOOLEAN) | (1L << SKIP) | (1L << VAR) | (1L << NOT) | (1L << LEFT_PARENTHESIS) | (1L << ID) | (1L << ATTRIBUTE_ID) | (1L << STRING) | (1L << NUMBER))) != 0) ); + } while ( (((_la) & ~0x3f) == 0 && ((1L << _la) & ((1L << 7) | (1L << FUNCTION) | (1L << FOREACH) | (1L << WHILE) | (1L << IF) | (1L << DO) | (1L << BOOLEAN) | (1L << SKIP) | (1L << VAR) | (1L << NOT) | (1L << RETURN) | (1L << LEFT_PARENTHESIS) | (1L << ID) | (1L << ATTRIBUTE_ID) | (1L << STRING) | (1L << NUMBER))) != 0) ); } } catch (RecognitionException re) { @@ -173,6 +172,7 @@ public final StatementsContext statements() throws RecognitionException { public static class StatementContext extends ParserRuleContext { public ExpressionContext Expression; + public ReturnStatementContext Return; public AttributesContext Attributes; public VariableDeclarationContext VariableDeclaration; public IfStatementContext If; @@ -192,6 +192,9 @@ public IfStatementContext ifStatement() { public SkipStatementContext skipStatement() { return getRuleContext(SkipStatementContext.class,0); } + public ReturnStatementContext returnStatement() { + return getRuleContext(ReturnStatementContext.class,0); + } public WhileStatementContext whileStatement() { return getRuleContext(WhileStatementContext.class,0); } @@ -228,102 +231,109 @@ public final StatementContext statement() throws RecognitionException { enterRule(_localctx, 4, RULE_statement); int _la; try { - setState(92); + setState(97); switch ( getInterpreter().adaptivePredict(_input,7,_ctx) ) { case 1: enterOuterAlt(_localctx, 1); { - setState(67); ((StatementContext)_localctx).Expression = expression(0); + setState(71); ((StatementContext)_localctx).Expression = expression(0); } break; case 2: enterOuterAlt(_localctx, 2); { - setState(69); - switch ( getInterpreter().adaptivePredict(_input,1,_ctx) ) { - case 1: - { - setState(68); ((StatementContext)_localctx).Attributes = attributes(); - } - break; - } - setState(71); ((StatementContext)_localctx).VariableDeclaration = variableDeclaration(); + setState(72); ((StatementContext)_localctx).Return = returnStatement(); } break; case 3: enterOuterAlt(_localctx, 3); { - setState(73); - _la = _input.LA(1); - if (_la==ATTRIBUTE_ID) { + setState(74); + switch ( getInterpreter().adaptivePredict(_input,1,_ctx) ) { + case 1: { - setState(72); ((StatementContext)_localctx).Attributes = attributes(); + setState(73); ((StatementContext)_localctx).Attributes = attributes(); } + break; } - - setState(75); ((StatementContext)_localctx).If = ifStatement(); + setState(76); ((StatementContext)_localctx).VariableDeclaration = variableDeclaration(); } break; case 4: enterOuterAlt(_localctx, 4); { - setState(77); + setState(78); _la = _input.LA(1); if (_la==ATTRIBUTE_ID) { { - setState(76); ((StatementContext)_localctx).Attributes = attributes(); + setState(77); ((StatementContext)_localctx).Attributes = attributes(); } } - setState(79); ((StatementContext)_localctx).Block = blockStatement(); + setState(80); ((StatementContext)_localctx).If = ifStatement(); } break; case 5: enterOuterAlt(_localctx, 5); { - setState(81); + setState(82); _la = _input.LA(1); if (_la==ATTRIBUTE_ID) { { - setState(80); ((StatementContext)_localctx).Attributes = attributes(); + setState(81); ((StatementContext)_localctx).Attributes = attributes(); } } - setState(83); ((StatementContext)_localctx).Foreach = foreachStatement(); + setState(84); ((StatementContext)_localctx).Block = blockStatement(); } break; case 6: enterOuterAlt(_localctx, 6); { - setState(85); + setState(86); _la = _input.LA(1); if (_la==ATTRIBUTE_ID) { { - setState(84); ((StatementContext)_localctx).Attributes = attributes(); + setState(85); ((StatementContext)_localctx).Attributes = attributes(); } } - setState(87); ((StatementContext)_localctx).While = whileStatement(); + setState(88); ((StatementContext)_localctx).Foreach = foreachStatement(); } break; case 7: enterOuterAlt(_localctx, 7); { - setState(89); + setState(90); + _la = _input.LA(1); + if (_la==ATTRIBUTE_ID) { + { + setState(89); ((StatementContext)_localctx).Attributes = attributes(); + } + } + + setState(92); ((StatementContext)_localctx).While = whileStatement(); + } + break; + + case 8: + enterOuterAlt(_localctx, 8); + { + setState(94); _la = _input.LA(1); if (_la==ATTRIBUTE_ID) { { - setState(88); ((StatementContext)_localctx).Attributes = attributes(); + setState(93); ((StatementContext)_localctx).Attributes = attributes(); } } - setState(91); ((StatementContext)_localctx).Skip = skipStatement(); + setState(96); ((StatementContext)_localctx).Skip = skipStatement(); } break; } @@ -382,27 +392,27 @@ public final VariableDeclarationContext variableDeclaration() throws Recognition try { enterOuterAlt(_localctx, 1); { - setState(95); + setState(100); _la = _input.LA(1); if (_la==ATTRIBUTE_ID) { { - setState(94); ((VariableDeclarationContext)_localctx).Attributes = attributes(); + setState(99); ((VariableDeclarationContext)_localctx).Attributes = attributes(); } } - setState(97); match(VAR); - setState(98); ((VariableDeclarationContext)_localctx).VariableName = match(ID); - setState(101); + setState(102); match(VAR); + setState(103); ((VariableDeclarationContext)_localctx).VariableName = match(ID); + setState(106); _la = _input.LA(1); if (_la==8) { { - setState(99); match(8); - setState(100); ((VariableDeclarationContext)_localctx).VariableType = typeName(); + setState(104); match(8); + setState(105); ((VariableDeclarationContext)_localctx).VariableType = typeName(); } } - setState(103); match(ASSIGN); - setState(104); ((VariableDeclarationContext)_localctx).VariableDefaultValue = expression(0); + setState(108); match(ASSIGN); + setState(109); ((VariableDeclarationContext)_localctx).VariableDefaultValue = expression(0); } } catch (RecognitionException re) { @@ -417,6 +427,7 @@ public final VariableDeclarationContext variableDeclaration() throws Recognition } public static class ExpressionContext extends ParserRuleContext { + public int _p; public ExpressionContext Left; public Token Operator; public ExpressionContext Expression; @@ -442,8 +453,10 @@ public List expression() { public PrimaryExpressionContext primaryExpression() { return getRuleContext(PrimaryExpressionContext.class,0); } - public ExpressionContext(ParserRuleContext parent, int invokingState) { + public ExpressionContext(ParserRuleContext parent, int invokingState) { super(parent, invokingState); } + public ExpressionContext(ParserRuleContext parent, int invokingState, int _p) { super(parent, invokingState); + this._p = _p; } @Override public int getRuleIndex() { return RULE_expression; } @Override @@ -461,50 +474,46 @@ public T accept(ParseTreeVisitor visitor) { } } - public final ExpressionContext expression() throws RecognitionException { - return expression(0); - } - - private ExpressionContext expression(int _p) throws RecognitionException { + public final ExpressionContext expression(int _p) throws RecognitionException { ParserRuleContext _parentctx = _ctx; int _parentState = getState(); - ExpressionContext _localctx = new ExpressionContext(_ctx, _parentState); + ExpressionContext _localctx = new ExpressionContext(_ctx, _parentState, _p); ExpressionContext _prevctx = _localctx; int _startState = 8; - enterRecursionRule(_localctx, 8, RULE_expression, _p); + enterRecursionRule(_localctx, RULE_expression); try { int _alt; enterOuterAlt(_localctx, 1); { - setState(112); + setState(117); switch ( getInterpreter().adaptivePredict(_input,10,_ctx) ) { case 1: { - setState(107); ((ExpressionContext)_localctx).Operator = match(NOT); - setState(108); ((ExpressionContext)_localctx).Expression = expression(13); + setState(112); ((ExpressionContext)_localctx).Operator = match(NOT); + setState(113); ((ExpressionContext)_localctx).Expression = expression(13); } break; case 2: { - setState(109); ((ExpressionContext)_localctx).PrimaryExpression = primaryExpression(); + setState(114); ((ExpressionContext)_localctx).PrimaryExpression = primaryExpression(); } break; case 3: { - setState(110); ((ExpressionContext)_localctx).FunctionCallExpression = functionCallExpression(); + setState(115); ((ExpressionContext)_localctx).FunctionCallExpression = functionCallExpression(); } break; case 4: { - setState(111); ((ExpressionContext)_localctx).MemberExpression = memberExpression(); + setState(116); ((ExpressionContext)_localctx).MemberExpression = memberExpression(); } break; } _ctx.stop = _input.LT(-1); - setState(152); + setState(157); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,12,_ctx); while ( _alt!=2 && _alt!=-1 ) { @@ -512,155 +521,155 @@ private ExpressionContext expression(int _p) throws RecognitionException { if ( _parseListeners!=null ) triggerExitRuleEvent(); _prevctx = _localctx; { - setState(150); + setState(155); switch ( getInterpreter().adaptivePredict(_input,11,_ctx) ) { case 1: { - _localctx = new ExpressionContext(_parentctx, _parentState); + _localctx = new ExpressionContext(_parentctx, _parentState, _p); _localctx.Left = _prevctx; pushNewRecursionContext(_localctx, _startState, RULE_expression); - setState(114); - if (!(precpred(_ctx, 12))) throw new FailedPredicateException(this, "precpred(_ctx, 12)"); - setState(115); ((ExpressionContext)_localctx).Operator = match(4); - setState(116); ((ExpressionContext)_localctx).Right = expression(13); + setState(119); + if (!(12 >= _localctx._p)) throw new FailedPredicateException(this, "12 >= $_p"); + setState(120); ((ExpressionContext)_localctx).Operator = match(4); + setState(121); ((ExpressionContext)_localctx).Right = expression(13); } break; case 2: { - _localctx = new ExpressionContext(_parentctx, _parentState); + _localctx = new ExpressionContext(_parentctx, _parentState, _p); _localctx.Left = _prevctx; pushNewRecursionContext(_localctx, _startState, RULE_expression); - setState(117); - if (!(precpred(_ctx, 11))) throw new FailedPredicateException(this, "precpred(_ctx, 11)"); - setState(118); ((ExpressionContext)_localctx).Operator = match(6); - setState(119); ((ExpressionContext)_localctx).Right = expression(12); + setState(122); + if (!(11 >= _localctx._p)) throw new FailedPredicateException(this, "11 >= $_p"); + setState(123); ((ExpressionContext)_localctx).Operator = match(6); + setState(124); ((ExpressionContext)_localctx).Right = expression(12); } break; case 3: { - _localctx = new ExpressionContext(_parentctx, _parentState); + _localctx = new ExpressionContext(_parentctx, _parentState, _p); _localctx.Left = _prevctx; pushNewRecursionContext(_localctx, _startState, RULE_expression); - setState(120); - if (!(precpred(_ctx, 10))) throw new FailedPredicateException(this, "precpred(_ctx, 10)"); - setState(121); ((ExpressionContext)_localctx).Operator = match(5); - setState(122); ((ExpressionContext)_localctx).Right = expression(11); + setState(125); + if (!(10 >= _localctx._p)) throw new FailedPredicateException(this, "10 >= $_p"); + setState(126); ((ExpressionContext)_localctx).Operator = match(5); + setState(127); ((ExpressionContext)_localctx).Right = expression(11); } break; case 4: { - _localctx = new ExpressionContext(_parentctx, _parentState); + _localctx = new ExpressionContext(_parentctx, _parentState, _p); _localctx.Left = _prevctx; pushNewRecursionContext(_localctx, _startState, RULE_expression); - setState(123); - if (!(precpred(_ctx, 9))) throw new FailedPredicateException(this, "precpred(_ctx, 9)"); - setState(124); ((ExpressionContext)_localctx).Operator = match(17); - setState(125); ((ExpressionContext)_localctx).Right = expression(10); + setState(128); + if (!(9 >= _localctx._p)) throw new FailedPredicateException(this, "9 >= $_p"); + setState(129); ((ExpressionContext)_localctx).Operator = match(17); + setState(130); ((ExpressionContext)_localctx).Right = expression(10); } break; case 5: { - _localctx = new ExpressionContext(_parentctx, _parentState); + _localctx = new ExpressionContext(_parentctx, _parentState, _p); _localctx.Left = _prevctx; pushNewRecursionContext(_localctx, _startState, RULE_expression); - setState(126); - if (!(precpred(_ctx, 8))) throw new FailedPredicateException(this, "precpred(_ctx, 8)"); - setState(127); ((ExpressionContext)_localctx).Operator = match(9); - setState(128); ((ExpressionContext)_localctx).Right = expression(9); + setState(131); + if (!(8 >= _localctx._p)) throw new FailedPredicateException(this, "8 >= $_p"); + setState(132); ((ExpressionContext)_localctx).Operator = match(9); + setState(133); ((ExpressionContext)_localctx).Right = expression(9); } break; case 6: { - _localctx = new ExpressionContext(_parentctx, _parentState); + _localctx = new ExpressionContext(_parentctx, _parentState, _p); _localctx.Left = _prevctx; pushNewRecursionContext(_localctx, _startState, RULE_expression); - setState(129); - if (!(precpred(_ctx, 7))) throw new FailedPredicateException(this, "precpred(_ctx, 7)"); - setState(130); ((ExpressionContext)_localctx).Operator = match(14); - setState(131); ((ExpressionContext)_localctx).Right = expression(8); + setState(134); + if (!(7 >= _localctx._p)) throw new FailedPredicateException(this, "7 >= $_p"); + setState(135); ((ExpressionContext)_localctx).Operator = match(14); + setState(136); ((ExpressionContext)_localctx).Right = expression(8); } break; case 7: { - _localctx = new ExpressionContext(_parentctx, _parentState); + _localctx = new ExpressionContext(_parentctx, _parentState, _p); _localctx.Left = _prevctx; pushNewRecursionContext(_localctx, _startState, RULE_expression); - setState(132); - if (!(precpred(_ctx, 6))) throw new FailedPredicateException(this, "precpred(_ctx, 6)"); - setState(133); ((ExpressionContext)_localctx).Operator = match(12); - setState(134); ((ExpressionContext)_localctx).Right = expression(7); + setState(137); + if (!(6 >= _localctx._p)) throw new FailedPredicateException(this, "6 >= $_p"); + setState(138); ((ExpressionContext)_localctx).Operator = match(12); + setState(139); ((ExpressionContext)_localctx).Right = expression(7); } break; case 8: { - _localctx = new ExpressionContext(_parentctx, _parentState); + _localctx = new ExpressionContext(_parentctx, _parentState, _p); _localctx.Left = _prevctx; pushNewRecursionContext(_localctx, _startState, RULE_expression); - setState(135); - if (!(precpred(_ctx, 5))) throw new FailedPredicateException(this, "precpred(_ctx, 5)"); - setState(136); ((ExpressionContext)_localctx).Operator = match(18); - setState(137); ((ExpressionContext)_localctx).Right = expression(6); + setState(140); + if (!(5 >= _localctx._p)) throw new FailedPredicateException(this, "5 >= $_p"); + setState(141); ((ExpressionContext)_localctx).Operator = match(18); + setState(142); ((ExpressionContext)_localctx).Right = expression(6); } break; case 9: { - _localctx = new ExpressionContext(_parentctx, _parentState); + _localctx = new ExpressionContext(_parentctx, _parentState, _p); _localctx.Left = _prevctx; pushNewRecursionContext(_localctx, _startState, RULE_expression); - setState(138); - if (!(precpred(_ctx, 4))) throw new FailedPredicateException(this, "precpred(_ctx, 4)"); - setState(139); ((ExpressionContext)_localctx).Operator = match(16); - setState(140); ((ExpressionContext)_localctx).Right = expression(5); + setState(143); + if (!(4 >= _localctx._p)) throw new FailedPredicateException(this, "4 >= $_p"); + setState(144); ((ExpressionContext)_localctx).Operator = match(16); + setState(145); ((ExpressionContext)_localctx).Right = expression(5); } break; case 10: { - _localctx = new ExpressionContext(_parentctx, _parentState); + _localctx = new ExpressionContext(_parentctx, _parentState, _p); _localctx.Left = _prevctx; pushNewRecursionContext(_localctx, _startState, RULE_expression); - setState(141); - if (!(precpred(_ctx, 3))) throw new FailedPredicateException(this, "precpred(_ctx, 3)"); - setState(142); ((ExpressionContext)_localctx).Operator = match(10); - setState(143); ((ExpressionContext)_localctx).Right = expression(4); + setState(146); + if (!(3 >= _localctx._p)) throw new FailedPredicateException(this, "3 >= $_p"); + setState(147); ((ExpressionContext)_localctx).Operator = match(10); + setState(148); ((ExpressionContext)_localctx).Right = expression(4); } break; case 11: { - _localctx = new ExpressionContext(_parentctx, _parentState); + _localctx = new ExpressionContext(_parentctx, _parentState, _p); _localctx.Left = _prevctx; pushNewRecursionContext(_localctx, _startState, RULE_expression); - setState(144); - if (!(precpred(_ctx, 2))) throw new FailedPredicateException(this, "precpred(_ctx, 2)"); - setState(145); ((ExpressionContext)_localctx).Operator = match(AND); - setState(146); ((ExpressionContext)_localctx).Right = expression(3); + setState(149); + if (!(2 >= _localctx._p)) throw new FailedPredicateException(this, "2 >= $_p"); + setState(150); ((ExpressionContext)_localctx).Operator = match(AND); + setState(151); ((ExpressionContext)_localctx).Right = expression(3); } break; case 12: { - _localctx = new ExpressionContext(_parentctx, _parentState); + _localctx = new ExpressionContext(_parentctx, _parentState, _p); _localctx.Left = _prevctx; pushNewRecursionContext(_localctx, _startState, RULE_expression); - setState(147); - if (!(precpred(_ctx, 1))) throw new FailedPredicateException(this, "precpred(_ctx, 1)"); - setState(148); ((ExpressionContext)_localctx).Operator = match(OR); - setState(149); ((ExpressionContext)_localctx).Right = expression(2); + setState(152); + if (!(1 >= _localctx._p)) throw new FailedPredicateException(this, "1 >= $_p"); + setState(153); ((ExpressionContext)_localctx).Operator = match(OR); + setState(154); ((ExpressionContext)_localctx).Right = expression(2); } break; } } } - setState(154); + setState(159); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,12,_ctx); } @@ -710,17 +719,17 @@ public final FunctionCallExpressionContext functionCallExpression() throws Recog try { enterOuterAlt(_localctx, 1); { - setState(155); primaryExpression(); - setState(156); match(LEFT_PARENTHESIS); - setState(158); + setState(160); primaryExpression(); + setState(161); match(LEFT_PARENTHESIS); + setState(163); _la = _input.LA(1); if ((((_la) & ~0x3f) == 0 && ((1L << _la) & ((1L << 7) | (1L << FUNCTION) | (1L << BOOLEAN) | (1L << NOT) | (1L << LEFT_PARENTHESIS) | (1L << ID) | (1L << ATTRIBUTE_ID) | (1L << STRING) | (1L << NUMBER))) != 0)) { { - setState(157); expression(0); + setState(162); expression(0); } } - setState(160); match(RIGHT_PARENTHESIS); + setState(165); match(RIGHT_PARENTHESIS); } } catch (RecognitionException re) { @@ -735,20 +744,17 @@ public final FunctionCallExpressionContext functionCallExpression() throws Recog } public static class MemberExpressionContext extends ParserRuleContext { - public FunctionCallExpressionContext functionCallExpression(int i) { - return getRuleContext(FunctionCallExpressionContext.class,i); - } - public List functionCallExpression() { - return getRuleContexts(FunctionCallExpressionContext.class); + public MemberTagExpressionContext memberTagExpression(int i) { + return getRuleContext(MemberTagExpressionContext.class,i); } - public IdentifierContext identifier(int i) { - return getRuleContext(IdentifierContext.class,i); + public FunctionCallExpressionContext functionCallExpression() { + return getRuleContext(FunctionCallExpressionContext.class,0); } public PrimaryExpressionContext primaryExpression() { return getRuleContext(PrimaryExpressionContext.class,0); } - public List identifier() { - return getRuleContexts(IdentifierContext.class); + public List memberTagExpression() { + return getRuleContexts(MemberTagExpressionContext.class); } public MemberExpressionContext(ParserRuleContext parent, int invokingState) { super(parent, invokingState); @@ -776,8 +782,21 @@ public final MemberExpressionContext memberExpression() throws RecognitionExcept int _alt; enterOuterAlt(_localctx, 1); { - setState(162); primaryExpression(); - setState(168); + setState(169); + switch ( getInterpreter().adaptivePredict(_input,14,_ctx) ) { + case 1: + { + setState(167); primaryExpression(); + } + break; + + case 2: + { + setState(168); functionCallExpression(); + } + break; + } + setState(173); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,15,_ctx); do { @@ -785,28 +804,15 @@ public final MemberExpressionContext memberExpression() throws RecognitionExcept case 1: { { - setState(163); match(2); - setState(166); - switch ( getInterpreter().adaptivePredict(_input,14,_ctx) ) { - case 1: - { - setState(164); identifier(); - } - break; - - case 2: - { - setState(165); functionCallExpression(); - } - break; - } + setState(171); match(2); + setState(172); memberTagExpression(); } } break; default: throw new NoViableAltException(this); } - setState(170); + setState(175); _errHandler.sync(this); _alt = getInterpreter().adaptivePredict(_input,15,_ctx); } while ( _alt!=2 && _alt!=-1 ); @@ -823,16 +829,75 @@ public final MemberExpressionContext memberExpression() throws RecognitionExcept return _localctx; } + public static class MemberTagExpressionContext extends ParserRuleContext { + public FunctionCallExpressionContext functionCallExpression() { + return getRuleContext(FunctionCallExpressionContext.class,0); + } + public IdentifierContext identifier() { + return getRuleContext(IdentifierContext.class,0); + } + public MemberTagExpressionContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_memberTagExpression; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof MetaCodeListener ) ((MetaCodeListener)listener).enterMemberTagExpression(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof MetaCodeListener ) ((MetaCodeListener)listener).exitMemberTagExpression(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof MetaCodeVisitor ) return ((MetaCodeVisitor)visitor).visitMemberTagExpression(this); + else return visitor.visitChildren(this); + } + } + + public final MemberTagExpressionContext memberTagExpression() throws RecognitionException { + MemberTagExpressionContext _localctx = new MemberTagExpressionContext(_ctx, getState()); + enterRule(_localctx, 14, RULE_memberTagExpression); + try { + setState(179); + switch ( getInterpreter().adaptivePredict(_input,16,_ctx) ) { + case 1: + enterOuterAlt(_localctx, 1); + { + setState(177); identifier(); + } + break; + + case 2: + enterOuterAlt(_localctx, 2); + { + setState(178); functionCallExpression(); + } + break; + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + public static class PrimaryExpressionContext extends ParserRuleContext { public AttributesContext Attributes; public ConstantContext Constant; - public IdentifierContext Id; + public Token Id; public FunctionExpressionContext Function; public AssignmentExpressionContext Assignment; public ExpressionContext InnerExpression; public AttributesContext attributes() { return getRuleContext(AttributesContext.class,0); } + public TerminalNode ID() { return getToken(MetaCodeParser.ID, 0); } public FunctionExpressionContext functionExpression() { return getRuleContext(FunctionExpressionContext.class,0); } @@ -845,9 +910,6 @@ public AssignmentExpressionContext assignmentExpression() { public ConstantContext constant() { return getRuleContext(ConstantContext.class,0); } - public IdentifierContext identifier() { - return getRuleContext(IdentifierContext.class,0); - } public PrimaryExpressionContext(ParserRuleContext parent, int invokingState) { super(parent, invokingState); } @@ -869,85 +931,85 @@ public T accept(ParseTreeVisitor visitor) { public final PrimaryExpressionContext primaryExpression() throws RecognitionException { PrimaryExpressionContext _localctx = new PrimaryExpressionContext(_ctx, getState()); - enterRule(_localctx, 14, RULE_primaryExpression); + enterRule(_localctx, 16, RULE_primaryExpression); int _la; try { - setState(195); - switch ( getInterpreter().adaptivePredict(_input,21,_ctx) ) { + setState(204); + switch ( getInterpreter().adaptivePredict(_input,22,_ctx) ) { case 1: enterOuterAlt(_localctx, 1); { - setState(173); + setState(182); _la = _input.LA(1); if (_la==ATTRIBUTE_ID) { { - setState(172); ((PrimaryExpressionContext)_localctx).Attributes = attributes(); + setState(181); ((PrimaryExpressionContext)_localctx).Attributes = attributes(); } } - setState(175); ((PrimaryExpressionContext)_localctx).Constant = constant(); + setState(184); ((PrimaryExpressionContext)_localctx).Constant = constant(); } break; case 2: enterOuterAlt(_localctx, 2); { - setState(177); + setState(186); _la = _input.LA(1); if (_la==ATTRIBUTE_ID) { { - setState(176); ((PrimaryExpressionContext)_localctx).Attributes = attributes(); + setState(185); ((PrimaryExpressionContext)_localctx).Attributes = attributes(); } } - setState(179); ((PrimaryExpressionContext)_localctx).Id = identifier(); + setState(188); ((PrimaryExpressionContext)_localctx).Id = match(ID); } break; case 3: enterOuterAlt(_localctx, 3); { - setState(181); + setState(190); _la = _input.LA(1); if (_la==ATTRIBUTE_ID) { { - setState(180); ((PrimaryExpressionContext)_localctx).Attributes = attributes(); + setState(189); ((PrimaryExpressionContext)_localctx).Attributes = attributes(); } } - setState(183); ((PrimaryExpressionContext)_localctx).Function = functionExpression(); + setState(192); ((PrimaryExpressionContext)_localctx).Function = functionExpression(); } break; case 4: enterOuterAlt(_localctx, 4); { - setState(185); + setState(194); _la = _input.LA(1); if (_la==ATTRIBUTE_ID) { { - setState(184); ((PrimaryExpressionContext)_localctx).Attributes = attributes(); + setState(193); ((PrimaryExpressionContext)_localctx).Attributes = attributes(); } } - setState(187); ((PrimaryExpressionContext)_localctx).Assignment = assignmentExpression(); + setState(196); ((PrimaryExpressionContext)_localctx).Assignment = assignmentExpression(); } break; case 5: enterOuterAlt(_localctx, 5); { - setState(189); + setState(198); _la = _input.LA(1); if (_la==ATTRIBUTE_ID) { { - setState(188); ((PrimaryExpressionContext)_localctx).Attributes = attributes(); + setState(197); ((PrimaryExpressionContext)_localctx).Attributes = attributes(); } } - setState(191); match(LEFT_PARENTHESIS); - setState(192); ((PrimaryExpressionContext)_localctx).InnerExpression = expression(0); - setState(193); match(RIGHT_PARENTHESIS); + setState(200); match(LEFT_PARENTHESIS); + setState(201); ((PrimaryExpressionContext)_localctx).InnerExpression = expression(0); + setState(202); match(RIGHT_PARENTHESIS); } break; } @@ -964,7 +1026,7 @@ public final PrimaryExpressionContext primaryExpression() throws RecognitionExce } public static class FunctionExpressionContext extends ParserRuleContext { - public IdentifierContext FunctionName; + public Token FunctionName; public FormalParameterListContext Parameters; public TypeNameContext ReturnType; public StatementsContext BodyStatements; @@ -976,6 +1038,7 @@ public StatementsContext statements() { return getRuleContext(StatementsContext.class,0); } public TerminalNode DO() { return getToken(MetaCodeParser.DO, 0); } + public TerminalNode ID() { return getToken(MetaCodeParser.ID, 0); } public TerminalNode FUNCTION() { return getToken(MetaCodeParser.FUNCTION, 0); } public ExpressionContext expression() { return getRuleContext(ExpressionContext.class,0); @@ -983,9 +1046,6 @@ public ExpressionContext expression() { public FormalParameterListContext formalParameterList() { return getRuleContext(FormalParameterListContext.class,0); } - public IdentifierContext identifier() { - return getRuleContext(IdentifierContext.class,0); - } public TerminalNode END() { return getToken(MetaCodeParser.END, 0); } public FunctionExpressionContext(ParserRuleContext parent, int invokingState) { super(parent, invokingState); @@ -1008,81 +1068,71 @@ public T accept(ParseTreeVisitor visitor) { public final FunctionExpressionContext functionExpression() throws RecognitionException { FunctionExpressionContext _localctx = new FunctionExpressionContext(_ctx, getState()); - enterRule(_localctx, 16, RULE_functionExpression); + enterRule(_localctx, 18, RULE_functionExpression); int _la; try { - setState(229); - switch ( getInterpreter().adaptivePredict(_input,28,_ctx) ) { + setState(237); + switch ( getInterpreter().adaptivePredict(_input,27,_ctx) ) { case 1: enterOuterAlt(_localctx, 1); { - setState(197); match(FUNCTION); - setState(199); + setState(206); match(FUNCTION); + setState(208); _la = _input.LA(1); if (_la==ID) { { - setState(198); ((FunctionExpressionContext)_localctx).FunctionName = identifier(); + setState(207); ((FunctionExpressionContext)_localctx).FunctionName = match(ID); } } - setState(201); match(LEFT_PARENTHESIS); - setState(203); + setState(210); match(LEFT_PARENTHESIS); + setState(212); _la = _input.LA(1); if (_la==ID || _la==ATTRIBUTE_ID) { { - setState(202); ((FunctionExpressionContext)_localctx).Parameters = formalParameterList(); + setState(211); ((FunctionExpressionContext)_localctx).Parameters = formalParameterList(); } } - setState(205); match(RIGHT_PARENTHESIS); - setState(208); - _la = _input.LA(1); - if (_la==8) { - { - setState(206); match(8); - setState(207); ((FunctionExpressionContext)_localctx).ReturnType = typeName(); - } + setState(214); match(RIGHT_PARENTHESIS); + { + setState(215); match(8); + setState(216); ((FunctionExpressionContext)_localctx).ReturnType = typeName(); } - - setState(210); match(DO); - setState(211); ((FunctionExpressionContext)_localctx).BodyStatements = statements(); - setState(212); match(END); + setState(218); match(DO); + setState(219); ((FunctionExpressionContext)_localctx).BodyStatements = statements(); + setState(220); match(END); } break; case 2: enterOuterAlt(_localctx, 2); { - setState(214); match(FUNCTION); - setState(216); + setState(222); match(FUNCTION); + setState(224); _la = _input.LA(1); if (_la==ID) { { - setState(215); ((FunctionExpressionContext)_localctx).FunctionName = identifier(); + setState(223); ((FunctionExpressionContext)_localctx).FunctionName = match(ID); } } - setState(218); match(LEFT_PARENTHESIS); - setState(220); + setState(226); match(LEFT_PARENTHESIS); + setState(228); _la = _input.LA(1); if (_la==ID || _la==ATTRIBUTE_ID) { { - setState(219); ((FunctionExpressionContext)_localctx).Parameters = formalParameterList(); + setState(227); ((FunctionExpressionContext)_localctx).Parameters = formalParameterList(); } } - setState(222); match(RIGHT_PARENTHESIS); - setState(225); - _la = _input.LA(1); - if (_la==8) { - { - setState(223); match(8); - setState(224); ((FunctionExpressionContext)_localctx).ReturnType = typeName(); - } + setState(230); match(RIGHT_PARENTHESIS); + { + setState(231); match(8); + setState(232); ((FunctionExpressionContext)_localctx).ReturnType = typeName(); } - - setState(227); match(ASSIGN); - setState(228); ((FunctionExpressionContext)_localctx).BodyExpression = expression(0); + setState(234); match(ASSIGN); + setState(235); ((FunctionExpressionContext)_localctx).BodyExpression = expression(0); } break; } @@ -1100,7 +1150,7 @@ public final FunctionExpressionContext functionExpression() throws RecognitionEx public static class ForeachStatementContext extends ParserRuleContext { public Token Var; - public IdentifierContext Id; + public Token Id; public TypeNameContext VariableType; public ExpressionContext ArrayExpression; public StatementContext Body; @@ -1112,13 +1162,11 @@ public TypeNameContext typeName() { return getRuleContext(TypeNameContext.class,0); } public TerminalNode VAR() { return getToken(MetaCodeParser.VAR, 0); } + public TerminalNode ID() { return getToken(MetaCodeParser.ID, 0); } public ExpressionContext expression() { return getRuleContext(ExpressionContext.class,0); } public TerminalNode FOREACH() { return getToken(MetaCodeParser.FOREACH, 0); } - public IdentifierContext identifier() { - return getRuleContext(IdentifierContext.class,0); - } public ForeachStatementContext(ParserRuleContext parent, int invokingState) { super(parent, invokingState); } @@ -1140,35 +1188,35 @@ public T accept(ParseTreeVisitor visitor) { public final ForeachStatementContext foreachStatement() throws RecognitionException { ForeachStatementContext _localctx = new ForeachStatementContext(_ctx, getState()); - enterRule(_localctx, 18, RULE_foreachStatement); + enterRule(_localctx, 20, RULE_foreachStatement); int _la; try { enterOuterAlt(_localctx, 1); { - setState(231); match(FOREACH); - setState(232); match(LEFT_PARENTHESIS); - setState(234); + setState(239); match(FOREACH); + setState(240); match(LEFT_PARENTHESIS); + setState(242); _la = _input.LA(1); if (_la==VAR) { { - setState(233); ((ForeachStatementContext)_localctx).Var = match(VAR); + setState(241); ((ForeachStatementContext)_localctx).Var = match(VAR); } } - setState(236); ((ForeachStatementContext)_localctx).Id = identifier(); - setState(239); + setState(244); ((ForeachStatementContext)_localctx).Id = match(ID); + setState(247); _la = _input.LA(1); if (_la==8) { { - setState(237); match(8); - setState(238); ((ForeachStatementContext)_localctx).VariableType = typeName(); + setState(245); match(8); + setState(246); ((ForeachStatementContext)_localctx).VariableType = typeName(); } } - setState(241); match(IN); - setState(242); ((ForeachStatementContext)_localctx).ArrayExpression = expression(0); - setState(243); match(RIGHT_PARENTHESIS); - setState(244); ((ForeachStatementContext)_localctx).Body = statement(); + setState(249); match(IN); + setState(250); ((ForeachStatementContext)_localctx).ArrayExpression = expression(0); + setState(251); match(RIGHT_PARENTHESIS); + setState(252); ((ForeachStatementContext)_localctx).Body = statement(); } } catch (RecognitionException re) { @@ -1213,15 +1261,15 @@ public T accept(ParseTreeVisitor visitor) { public final WhileStatementContext whileStatement() throws RecognitionException { WhileStatementContext _localctx = new WhileStatementContext(_ctx, getState()); - enterRule(_localctx, 20, RULE_whileStatement); + enterRule(_localctx, 22, RULE_whileStatement); try { enterOuterAlt(_localctx, 1); { - setState(246); match(WHILE); - setState(247); match(LEFT_PARENTHESIS); - setState(248); ((WhileStatementContext)_localctx).ConditionExpression = expression(0); - setState(249); match(RIGHT_PARENTHESIS); - setState(250); ((WhileStatementContext)_localctx).Body = statement(); + setState(254); match(WHILE); + setState(255); match(LEFT_PARENTHESIS); + setState(256); ((WhileStatementContext)_localctx).ConditionExpression = expression(0); + setState(257); match(RIGHT_PARENTHESIS); + setState(258); ((WhileStatementContext)_localctx).Body = statement(); } } catch (RecognitionException re) { @@ -1263,13 +1311,13 @@ public T accept(ParseTreeVisitor visitor) { public final BlockStatementContext blockStatement() throws RecognitionException { BlockStatementContext _localctx = new BlockStatementContext(_ctx, getState()); - enterRule(_localctx, 22, RULE_blockStatement); + enterRule(_localctx, 24, RULE_blockStatement); try { enterOuterAlt(_localctx, 1); { - setState(252); match(DO); - setState(253); ((BlockStatementContext)_localctx).Body = statements(); - setState(254); match(END); + setState(260); match(DO); + setState(261); ((BlockStatementContext)_localctx).Body = statements(); + setState(262); match(END); } } catch (RecognitionException re) { @@ -1306,11 +1354,56 @@ public T accept(ParseTreeVisitor visitor) { public final SkipStatementContext skipStatement() throws RecognitionException { SkipStatementContext _localctx = new SkipStatementContext(_ctx, getState()); - enterRule(_localctx, 24, RULE_skipStatement); + enterRule(_localctx, 26, RULE_skipStatement); + try { + enterOuterAlt(_localctx, 1); + { + setState(264); match(SKIP); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + public static class ReturnStatementContext extends ParserRuleContext { + public TerminalNode RETURN() { return getToken(MetaCodeParser.RETURN, 0); } + public ExpressionContext expression() { + return getRuleContext(ExpressionContext.class,0); + } + public ReturnStatementContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_returnStatement; } + @Override + public void enterRule(ParseTreeListener listener) { + if ( listener instanceof MetaCodeListener ) ((MetaCodeListener)listener).enterReturnStatement(this); + } + @Override + public void exitRule(ParseTreeListener listener) { + if ( listener instanceof MetaCodeListener ) ((MetaCodeListener)listener).exitReturnStatement(this); + } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof MetaCodeVisitor ) return ((MetaCodeVisitor)visitor).visitReturnStatement(this); + else return visitor.visitChildren(this); + } + } + + public final ReturnStatementContext returnStatement() throws RecognitionException { + ReturnStatementContext _localctx = new ReturnStatementContext(_ctx, getState()); + enterRule(_localctx, 28, RULE_returnStatement); try { enterOuterAlt(_localctx, 1); { - setState(256); match(SKIP); + setState(266); match(RETURN); + setState(267); expression(0); } } catch (RecognitionException re) { @@ -1325,7 +1418,7 @@ public final SkipStatementContext skipStatement() throws RecognitionException { } public static class AssignmentExpressionContext extends ParserRuleContext { - public IdentifierContext Variable; + public Token Variable; public ExpressionContext Value; public AttributesContext ConditionalAttributes; public ExpressionContext ConditionalExpression; @@ -1337,12 +1430,10 @@ public ExpressionContext expression(int i) { return getRuleContext(ExpressionContext.class,i); } public TerminalNode ASSIGN() { return getToken(MetaCodeParser.ASSIGN, 0); } + public TerminalNode ID() { return getToken(MetaCodeParser.ID, 0); } public List expression() { return getRuleContexts(ExpressionContext.class); } - public IdentifierContext identifier() { - return getRuleContext(IdentifierContext.class,0); - } public AssignmentExpressionContext(ParserRuleContext parent, int invokingState) { super(parent, invokingState); } @@ -1364,30 +1455,30 @@ public T accept(ParseTreeVisitor visitor) { public final AssignmentExpressionContext assignmentExpression() throws RecognitionException { AssignmentExpressionContext _localctx = new AssignmentExpressionContext(_ctx, getState()); - enterRule(_localctx, 26, RULE_assignmentExpression); + enterRule(_localctx, 30, RULE_assignmentExpression); int _la; try { enterOuterAlt(_localctx, 1); { - setState(258); ((AssignmentExpressionContext)_localctx).Variable = identifier(); - setState(259); match(ASSIGN); - setState(260); ((AssignmentExpressionContext)_localctx).Value = expression(0); - setState(269); - switch ( getInterpreter().adaptivePredict(_input,32,_ctx) ) { + setState(269); ((AssignmentExpressionContext)_localctx).Variable = match(ID); + setState(270); match(ASSIGN); + setState(271); ((AssignmentExpressionContext)_localctx).Value = expression(0); + setState(280); + switch ( getInterpreter().adaptivePredict(_input,31,_ctx) ) { case 1: { - setState(262); + setState(273); _la = _input.LA(1); if (_la==ATTRIBUTE_ID) { { - setState(261); ((AssignmentExpressionContext)_localctx).ConditionalAttributes = attributes(); + setState(272); ((AssignmentExpressionContext)_localctx).ConditionalAttributes = attributes(); } } - setState(264); match(IF); - setState(265); match(LEFT_PARENTHESIS); - setState(266); ((AssignmentExpressionContext)_localctx).ConditionalExpression = expression(0); - setState(267); match(RIGHT_PARENTHESIS); + setState(275); match(IF); + setState(276); match(LEFT_PARENTHESIS); + setState(277); ((AssignmentExpressionContext)_localctx).ConditionalExpression = expression(0); + setState(278); match(RIGHT_PARENTHESIS); } break; } @@ -1407,7 +1498,7 @@ public final AssignmentExpressionContext assignmentExpression() throws Recogniti public static class IfStatementContext extends ParserRuleContext { public ExpressionContext Condition; public StatementsContext Statements; - public ElseIfStatementContext ElseIfExpressions; + public ElseIfStatementContext ElseIfStatements; public StatementsContext ElseStatements; public TerminalNode IF() { return getToken(MetaCodeParser.IF, 0); } public List statements() { @@ -1448,42 +1539,42 @@ public T accept(ParseTreeVisitor visitor) { public final IfStatementContext ifStatement() throws RecognitionException { IfStatementContext _localctx = new IfStatementContext(_ctx, getState()); - enterRule(_localctx, 28, RULE_ifStatement); + enterRule(_localctx, 32, RULE_ifStatement); int _la; try { int _alt; enterOuterAlt(_localctx, 1); { - setState(271); match(IF); - setState(272); match(LEFT_PARENTHESIS); - setState(273); ((IfStatementContext)_localctx).Condition = expression(0); - setState(274); match(RIGHT_PARENTHESIS); - setState(275); ((IfStatementContext)_localctx).Statements = statements(); - setState(279); + setState(282); match(IF); + setState(283); match(LEFT_PARENTHESIS); + setState(284); ((IfStatementContext)_localctx).Condition = expression(0); + setState(285); match(RIGHT_PARENTHESIS); + setState(286); ((IfStatementContext)_localctx).Statements = statements(); + setState(290); _errHandler.sync(this); - _alt = getInterpreter().adaptivePredict(_input,33,_ctx); + _alt = getInterpreter().adaptivePredict(_input,32,_ctx); while ( _alt!=2 && _alt!=-1 ) { if ( _alt==1 ) { { { - setState(276); ((IfStatementContext)_localctx).ElseIfExpressions = elseIfStatement(); + setState(287); ((IfStatementContext)_localctx).ElseIfStatements = elseIfStatement(); } } } - setState(281); + setState(292); _errHandler.sync(this); - _alt = getInterpreter().adaptivePredict(_input,33,_ctx); + _alt = getInterpreter().adaptivePredict(_input,32,_ctx); } - setState(284); + setState(295); _la = _input.LA(1); if (_la==ELSE) { { - setState(282); match(ELSE); - setState(283); ((IfStatementContext)_localctx).ElseStatements = statements(); + setState(293); match(ELSE); + setState(294); ((IfStatementContext)_localctx).ElseStatements = statements(); } } - setState(286); match(END); + setState(297); match(END); } } catch (RecognitionException re) { @@ -1498,6 +1589,8 @@ public final IfStatementContext ifStatement() throws RecognitionException { } public static class ElseIfStatementContext extends ParserRuleContext { + public ExpressionContext Condition; + public StatementsContext Statements; public TerminalNode IF() { return getToken(MetaCodeParser.IF, 0); } public StatementsContext statements() { return getRuleContext(StatementsContext.class,0); @@ -1527,16 +1620,16 @@ public T accept(ParseTreeVisitor visitor) { public final ElseIfStatementContext elseIfStatement() throws RecognitionException { ElseIfStatementContext _localctx = new ElseIfStatementContext(_ctx, getState()); - enterRule(_localctx, 30, RULE_elseIfStatement); + enterRule(_localctx, 34, RULE_elseIfStatement); try { enterOuterAlt(_localctx, 1); { - setState(288); match(ELSE); - setState(289); match(IF); - setState(290); match(LEFT_PARENTHESIS); - setState(291); expression(0); - setState(292); match(RIGHT_PARENTHESIS); - setState(293); statements(); + setState(299); match(ELSE); + setState(300); match(IF); + setState(301); match(LEFT_PARENTHESIS); + setState(302); ((ElseIfStatementContext)_localctx).Condition = expression(0); + setState(303); match(RIGHT_PARENTHESIS); + setState(304); ((ElseIfStatementContext)_localctx).Statements = statements(); } } catch (RecognitionException re) { @@ -1578,23 +1671,23 @@ public T accept(ParseTreeVisitor visitor) { public final FormalParameterListContext formalParameterList() throws RecognitionException { FormalParameterListContext _localctx = new FormalParameterListContext(_ctx, getState()); - enterRule(_localctx, 32, RULE_formalParameterList); + enterRule(_localctx, 36, RULE_formalParameterList); int _la; try { enterOuterAlt(_localctx, 1); { - setState(295); formalParameter(); - setState(300); + setState(306); formalParameter(); + setState(311); _errHandler.sync(this); _la = _input.LA(1); while (_la==3) { { { - setState(296); match(3); - setState(297); formalParameter(); + setState(307); match(3); + setState(308); formalParameter(); } } - setState(302); + setState(313); _errHandler.sync(this); _la = _input.LA(1); } @@ -1612,15 +1705,16 @@ public final FormalParameterListContext formalParameterList() throws Recognition } public static class FormalParameterContext extends ParserRuleContext { + public AttributesContext Attributes; + public Token Name; + public TypeNameContext Type; public AttributesContext attributes() { return getRuleContext(AttributesContext.class,0); } public TypeNameContext typeName() { return getRuleContext(TypeNameContext.class,0); } - public IdentifierContext identifier() { - return getRuleContext(IdentifierContext.class,0); - } + public TerminalNode ID() { return getToken(MetaCodeParser.ID, 0); } public FormalParameterContext(ParserRuleContext parent, int invokingState) { super(parent, invokingState); } @@ -1642,22 +1736,22 @@ public T accept(ParseTreeVisitor visitor) { public final FormalParameterContext formalParameter() throws RecognitionException { FormalParameterContext _localctx = new FormalParameterContext(_ctx, getState()); - enterRule(_localctx, 34, RULE_formalParameter); + enterRule(_localctx, 38, RULE_formalParameter); int _la; try { enterOuterAlt(_localctx, 1); { - setState(304); + setState(315); _la = _input.LA(1); if (_la==ATTRIBUTE_ID) { { - setState(303); attributes(); + setState(314); ((FormalParameterContext)_localctx).Attributes = attributes(); } } - setState(306); identifier(); - setState(307); match(8); - setState(308); typeName(); + setState(317); ((FormalParameterContext)_localctx).Name = match(ID); + setState(318); match(8); + setState(319); ((FormalParameterContext)_localctx).Type = typeName(); } } catch (RecognitionException re) { @@ -1699,23 +1793,23 @@ public T accept(ParseTreeVisitor visitor) { public final ActualParameterListContext actualParameterList() throws RecognitionException { ActualParameterListContext _localctx = new ActualParameterListContext(_ctx, getState()); - enterRule(_localctx, 36, RULE_actualParameterList); + enterRule(_localctx, 40, RULE_actualParameterList); int _la; try { enterOuterAlt(_localctx, 1); { - setState(310); expression(0); - setState(315); + setState(321); expression(0); + setState(326); _errHandler.sync(this); _la = _input.LA(1); while (_la==3) { { { - setState(311); match(3); - setState(312); expression(0); + setState(322); match(3); + setState(323); expression(0); } } - setState(317); + setState(328); _errHandler.sync(this); _la = _input.LA(1); } @@ -1761,31 +1855,31 @@ public T accept(ParseTreeVisitor visitor) { public final TypeNameContext typeName() throws RecognitionException { TypeNameContext _localctx = new TypeNameContext(_ctx, getState()); - enterRule(_localctx, 38, RULE_typeName); + enterRule(_localctx, 42, RULE_typeName); int _la; try { enterOuterAlt(_localctx, 1); { - setState(319); + setState(330); _la = _input.LA(1); if (_la==ATTRIBUTE_ID) { { - setState(318); attributes(); + setState(329); attributes(); } } - setState(321); match(ID); - setState(326); + setState(332); match(ID); + setState(337); _errHandler.sync(this); _la = _input.LA(1); while (_la==2) { { { - setState(322); match(2); - setState(323); match(ID); + setState(333); match(2); + setState(334); match(ID); } } - setState(328); + setState(339); _errHandler.sync(this); _la = _input.LA(1); } @@ -1844,42 +1938,42 @@ public T accept(ParseTreeVisitor visitor) { public final ConstantContext constant() throws RecognitionException { ConstantContext _localctx = new ConstantContext(_ctx, getState()); - enterRule(_localctx, 40, RULE_constant); + enterRule(_localctx, 44, RULE_constant); try { - setState(334); - switch ( getInterpreter().adaptivePredict(_input,40,_ctx) ) { + setState(345); + switch ( getInterpreter().adaptivePredict(_input,39,_ctx) ) { case 1: enterOuterAlt(_localctx, 1); { - setState(329); ((ConstantContext)_localctx).Number = numberConstant(); + setState(340); ((ConstantContext)_localctx).Number = numberConstant(); } break; case 2: enterOuterAlt(_localctx, 2); { - setState(330); ((ConstantContext)_localctx).String = stringConstant(); + setState(341); ((ConstantContext)_localctx).String = stringConstant(); } break; case 3: enterOuterAlt(_localctx, 3); { - setState(331); ((ConstantContext)_localctx).Boolean = booleanConstant(); + setState(342); ((ConstantContext)_localctx).Boolean = booleanConstant(); } break; case 4: enterOuterAlt(_localctx, 4); { - setState(332); ((ConstantContext)_localctx).Array = arrayConstant(); + setState(343); ((ConstantContext)_localctx).Array = arrayConstant(); } break; case 5: enterOuterAlt(_localctx, 5); { - setState(333); ((ConstantContext)_localctx).Interval = intervalConstant(); + setState(344); ((ConstantContext)_localctx).Interval = intervalConstant(); } break; } @@ -1919,11 +2013,11 @@ public T accept(ParseTreeVisitor visitor) { public final IdentifierContext identifier() throws RecognitionException { IdentifierContext _localctx = new IdentifierContext(_ctx, getState()); - enterRule(_localctx, 42, RULE_identifier); + enterRule(_localctx, 46, RULE_identifier); try { enterOuterAlt(_localctx, 1); { - setState(336); ((IdentifierContext)_localctx).Id = match(ID); + setState(347); ((IdentifierContext)_localctx).Id = match(ID); } } catch (RecognitionException re) { @@ -1960,11 +2054,11 @@ public T accept(ParseTreeVisitor visitor) { public final NumberConstantContext numberConstant() throws RecognitionException { NumberConstantContext _localctx = new NumberConstantContext(_ctx, getState()); - enterRule(_localctx, 44, RULE_numberConstant); + enterRule(_localctx, 48, RULE_numberConstant); try { enterOuterAlt(_localctx, 1); { - setState(338); match(NUMBER); + setState(349); match(NUMBER); } } catch (RecognitionException re) { @@ -2001,11 +2095,11 @@ public T accept(ParseTreeVisitor visitor) { public final StringConstantContext stringConstant() throws RecognitionException { StringConstantContext _localctx = new StringConstantContext(_ctx, getState()); - enterRule(_localctx, 46, RULE_stringConstant); + enterRule(_localctx, 50, RULE_stringConstant); try { enterOuterAlt(_localctx, 1); { - setState(340); match(STRING); + setState(351); match(STRING); } } catch (RecognitionException re) { @@ -2042,11 +2136,11 @@ public T accept(ParseTreeVisitor visitor) { public final BooleanConstantContext booleanConstant() throws RecognitionException { BooleanConstantContext _localctx = new BooleanConstantContext(_ctx, getState()); - enterRule(_localctx, 48, RULE_booleanConstant); + enterRule(_localctx, 52, RULE_booleanConstant); try { enterOuterAlt(_localctx, 1); { - setState(342); match(BOOLEAN); + setState(353); match(BOOLEAN); } } catch (RecognitionException re) { @@ -2088,39 +2182,39 @@ public T accept(ParseTreeVisitor visitor) { public final ArrayConstantContext arrayConstant() throws RecognitionException { ArrayConstantContext _localctx = new ArrayConstantContext(_ctx, getState()); - enterRule(_localctx, 50, RULE_arrayConstant); + enterRule(_localctx, 54, RULE_arrayConstant); int _la; try { - setState(357); - switch ( getInterpreter().adaptivePredict(_input,42,_ctx) ) { + setState(368); + switch ( getInterpreter().adaptivePredict(_input,41,_ctx) ) { case 1: enterOuterAlt(_localctx, 1); { - setState(344); match(7); - setState(345); expression(0); - setState(350); + setState(355); match(7); + setState(356); expression(0); + setState(361); _errHandler.sync(this); _la = _input.LA(1); while (_la==3) { { { - setState(346); match(3); - setState(347); expression(0); + setState(357); match(3); + setState(358); expression(0); } } - setState(352); + setState(363); _errHandler.sync(this); _la = _input.LA(1); } - setState(353); match(1); + setState(364); match(1); } break; case 2: enterOuterAlt(_localctx, 2); { - setState(355); match(7); - setState(356); match(1); + setState(366); match(7); + setState(367); match(1); } break; } @@ -2165,19 +2259,19 @@ public T accept(ParseTreeVisitor visitor) { public final IntervalConstantContext intervalConstant() throws RecognitionException { IntervalConstantContext _localctx = new IntervalConstantContext(_ctx, getState()); - enterRule(_localctx, 52, RULE_intervalConstant); + enterRule(_localctx, 56, RULE_intervalConstant); try { enterOuterAlt(_localctx, 1); { - setState(359); ((IntervalConstantContext)_localctx).Start = match(NUMBER); - setState(360); match(13); - setState(361); ((IntervalConstantContext)_localctx).End = match(NUMBER); - setState(364); - switch ( getInterpreter().adaptivePredict(_input,43,_ctx) ) { + setState(370); ((IntervalConstantContext)_localctx).Start = match(NUMBER); + setState(371); match(13); + setState(372); ((IntervalConstantContext)_localctx).End = match(NUMBER); + setState(375); + switch ( getInterpreter().adaptivePredict(_input,42,_ctx) ) { case 1: { - setState(362); match(15); - setState(363); ((IntervalConstantContext)_localctx).By = match(NUMBER); + setState(373); match(15); + setState(374); ((IntervalConstantContext)_localctx).By = match(NUMBER); } break; } @@ -2222,29 +2316,29 @@ public T accept(ParseTreeVisitor visitor) { public final AttributesContext attributes() throws RecognitionException { AttributesContext _localctx = new AttributesContext(_ctx, getState()); - enterRule(_localctx, 54, RULE_attributes); + enterRule(_localctx, 58, RULE_attributes); try { int _alt; enterOuterAlt(_localctx, 1); { - setState(367); + setState(378); _errHandler.sync(this); - _alt = getInterpreter().adaptivePredict(_input,44,_ctx); + _alt = getInterpreter().adaptivePredict(_input,43,_ctx); do { switch (_alt) { case 1: { { - setState(366); attribute(); + setState(377); attribute(); } } break; default: throw new NoViableAltException(this); } - setState(369); + setState(380); _errHandler.sync(this); - _alt = getInterpreter().adaptivePredict(_input,44,_ctx); + _alt = getInterpreter().adaptivePredict(_input,43,_ctx); } while ( _alt!=2 && _alt!=-1 ); } } @@ -2289,33 +2383,33 @@ public T accept(ParseTreeVisitor visitor) { public final AttributeContext attribute() throws RecognitionException { AttributeContext _localctx = new AttributeContext(_ctx, getState()); - enterRule(_localctx, 56, RULE_attribute); + enterRule(_localctx, 60, RULE_attribute); int _la; try { enterOuterAlt(_localctx, 1); { - setState(371); ((AttributeContext)_localctx).Name = match(ATTRIBUTE_ID); - setState(383); - switch ( getInterpreter().adaptivePredict(_input,46,_ctx) ) { + setState(382); ((AttributeContext)_localctx).Name = match(ATTRIBUTE_ID); + setState(394); + switch ( getInterpreter().adaptivePredict(_input,45,_ctx) ) { case 1: { - setState(372); match(LEFT_PARENTHESIS); - setState(373); constant(); - setState(378); + setState(383); match(LEFT_PARENTHESIS); + setState(384); constant(); + setState(389); _errHandler.sync(this); _la = _input.LA(1); while (_la==3) { { { - setState(374); match(3); - setState(375); constant(); + setState(385); match(3); + setState(386); constant(); } } - setState(380); + setState(391); _errHandler.sync(this); _la = _input.LA(1); } - setState(381); match(RIGHT_PARENTHESIS); + setState(392); match(RIGHT_PARENTHESIS); } break; } @@ -2340,176 +2434,179 @@ public boolean sempred(RuleContext _localctx, int ruleIndex, int predIndex) { } private boolean expression_sempred(ExpressionContext _localctx, int predIndex) { switch (predIndex) { - case 0: return precpred(_ctx, 12); + case 0: return 12 >= _localctx._p; - case 1: return precpred(_ctx, 11); + case 1: return 11 >= _localctx._p; - case 2: return precpred(_ctx, 10); + case 2: return 10 >= _localctx._p; - case 3: return precpred(_ctx, 9); + case 3: return 9 >= _localctx._p; - case 4: return precpred(_ctx, 8); + case 4: return 8 >= _localctx._p; - case 5: return precpred(_ctx, 7); + case 5: return 7 >= _localctx._p; - case 6: return precpred(_ctx, 6); + case 6: return 6 >= _localctx._p; - case 7: return precpred(_ctx, 5); + case 7: return 5 >= _localctx._p; - case 8: return precpred(_ctx, 4); + case 8: return 4 >= _localctx._p; - case 9: return precpred(_ctx, 3); + case 9: return 3 >= _localctx._p; - case 10: return precpred(_ctx, 2); + case 10: return 2 >= _localctx._p; - case 11: return precpred(_ctx, 1); + case 11: return 1 >= _localctx._p; } return true; } public static final String _serializedATN = - "\3\u0430\ud6d1\u8206\uad2d\u4417\uaef1\u8d80\uaadd\3.\u0184\4\2\t\2\4"+ + "\3\uacf5\uee8c\u4f5d\u8b0d\u4a45\u78bd\u1b2f\u3378\3/\u018f\4\2\t\2\4"+ "\3\t\3\4\4\t\4\4\5\t\5\4\6\t\6\4\7\t\7\4\b\t\b\4\t\t\t\4\n\t\n\4\13\t"+ "\13\4\f\t\f\4\r\t\r\4\16\t\16\4\17\t\17\4\20\t\20\4\21\t\21\4\22\t\22"+ "\4\23\t\23\4\24\t\24\4\25\t\25\4\26\t\26\4\27\t\27\4\30\t\30\4\31\t\31"+ - "\4\32\t\32\4\33\t\33\4\34\t\34\4\35\t\35\4\36\t\36\3\2\3\2\3\3\3\3\3\3"+ - "\6\3B\n\3\r\3\16\3C\3\4\3\4\5\4H\n\4\3\4\3\4\5\4L\n\4\3\4\3\4\5\4P\n\4"+ - "\3\4\3\4\5\4T\n\4\3\4\3\4\5\4X\n\4\3\4\3\4\5\4\\\n\4\3\4\5\4_\n\4\3\5"+ - "\5\5b\n\5\3\5\3\5\3\5\3\5\5\5h\n\5\3\5\3\5\3\5\3\6\3\6\3\6\3\6\3\6\3\6"+ - "\5\6s\n\6\3\6\3\6\3\6\3\6\3\6\3\6\3\6\3\6\3\6\3\6\3\6\3\6\3\6\3\6\3\6"+ + "\4\32\t\32\4\33\t\33\4\34\t\34\4\35\t\35\4\36\t\36\4\37\t\37\4 \t \3\2"+ + "\3\2\3\3\3\3\3\3\6\3F\n\3\r\3\16\3G\3\4\3\4\3\4\5\4M\n\4\3\4\3\4\5\4Q"+ + "\n\4\3\4\3\4\5\4U\n\4\3\4\3\4\5\4Y\n\4\3\4\3\4\5\4]\n\4\3\4\3\4\5\4a\n"+ + "\4\3\4\5\4d\n\4\3\5\5\5g\n\5\3\5\3\5\3\5\3\5\5\5m\n\5\3\5\3\5\3\5\3\6"+ + "\3\6\3\6\3\6\3\6\3\6\5\6x\n\6\3\6\3\6\3\6\3\6\3\6\3\6\3\6\3\6\3\6\3\6"+ "\3\6\3\6\3\6\3\6\3\6\3\6\3\6\3\6\3\6\3\6\3\6\3\6\3\6\3\6\3\6\3\6\3\6\3"+ - "\6\3\6\3\6\3\6\7\6\u0099\n\6\f\6\16\6\u009c\13\6\3\7\3\7\3\7\5\7\u00a1"+ - "\n\7\3\7\3\7\3\b\3\b\3\b\3\b\5\b\u00a9\n\b\6\b\u00ab\n\b\r\b\16\b\u00ac"+ - "\3\t\5\t\u00b0\n\t\3\t\3\t\5\t\u00b4\n\t\3\t\3\t\5\t\u00b8\n\t\3\t\3\t"+ - "\5\t\u00bc\n\t\3\t\3\t\5\t\u00c0\n\t\3\t\3\t\3\t\3\t\5\t\u00c6\n\t\3\n"+ - "\3\n\5\n\u00ca\n\n\3\n\3\n\5\n\u00ce\n\n\3\n\3\n\3\n\5\n\u00d3\n\n\3\n"+ - "\3\n\3\n\3\n\3\n\3\n\5\n\u00db\n\n\3\n\3\n\5\n\u00df\n\n\3\n\3\n\3\n\5"+ - "\n\u00e4\n\n\3\n\3\n\5\n\u00e8\n\n\3\13\3\13\3\13\5\13\u00ed\n\13\3\13"+ - "\3\13\3\13\5\13\u00f2\n\13\3\13\3\13\3\13\3\13\3\13\3\f\3\f\3\f\3\f\3"+ - "\f\3\f\3\r\3\r\3\r\3\r\3\16\3\16\3\17\3\17\3\17\3\17\5\17\u0109\n\17\3"+ - "\17\3\17\3\17\3\17\3\17\5\17\u0110\n\17\3\20\3\20\3\20\3\20\3\20\3\20"+ - "\7\20\u0118\n\20\f\20\16\20\u011b\13\20\3\20\3\20\5\20\u011f\n\20\3\20"+ - "\3\20\3\21\3\21\3\21\3\21\3\21\3\21\3\21\3\22\3\22\3\22\7\22\u012d\n\22"+ - "\f\22\16\22\u0130\13\22\3\23\5\23\u0133\n\23\3\23\3\23\3\23\3\23\3\24"+ - "\3\24\3\24\7\24\u013c\n\24\f\24\16\24\u013f\13\24\3\25\5\25\u0142\n\25"+ - "\3\25\3\25\3\25\7\25\u0147\n\25\f\25\16\25\u014a\13\25\3\26\3\26\3\26"+ - "\3\26\3\26\5\26\u0151\n\26\3\27\3\27\3\30\3\30\3\31\3\31\3\32\3\32\3\33"+ - "\3\33\3\33\3\33\7\33\u015f\n\33\f\33\16\33\u0162\13\33\3\33\3\33\3\33"+ - "\3\33\5\33\u0168\n\33\3\34\3\34\3\34\3\34\3\34\5\34\u016f\n\34\3\35\6"+ - "\35\u0172\n\35\r\35\16\35\u0173\3\36\3\36\3\36\3\36\3\36\7\36\u017b\n"+ - "\36\f\36\16\36\u017e\13\36\3\36\3\36\5\36\u0182\n\36\3\36\2\3\n\37\2\4"+ - "\6\b\n\f\16\20\22\24\26\30\32\34\36 \"$&(*,.\60\62\64\668:\2\2\u01ac\2"+ - "<\3\2\2\2\4A\3\2\2\2\6^\3\2\2\2\ba\3\2\2\2\nr\3\2\2\2\f\u009d\3\2\2\2"+ - "\16\u00a4\3\2\2\2\20\u00c5\3\2\2\2\22\u00e7\3\2\2\2\24\u00e9\3\2\2\2\26"+ - "\u00f8\3\2\2\2\30\u00fe\3\2\2\2\32\u0102\3\2\2\2\34\u0104\3\2\2\2\36\u0111"+ - "\3\2\2\2 \u0122\3\2\2\2\"\u0129\3\2\2\2$\u0132\3\2\2\2&\u0138\3\2\2\2"+ - "(\u0141\3\2\2\2*\u0150\3\2\2\2,\u0152\3\2\2\2.\u0154\3\2\2\2\60\u0156"+ - "\3\2\2\2\62\u0158\3\2\2\2\64\u0167\3\2\2\2\66\u0169\3\2\2\28\u0171\3\2"+ - "\2\2:\u0175\3\2\2\2<=\5\4\3\2=\3\3\2\2\2>?\5\6\4\2?@\7\r\2\2@B\3\2\2\2"+ - "A>\3\2\2\2BC\3\2\2\2CA\3\2\2\2CD\3\2\2\2D\5\3\2\2\2E_\5\n\6\2FH\58\35"+ - "\2GF\3\2\2\2GH\3\2\2\2HI\3\2\2\2I_\5\b\5\2JL\58\35\2KJ\3\2\2\2KL\3\2\2"+ - "\2LM\3\2\2\2M_\5\36\20\2NP\58\35\2ON\3\2\2\2OP\3\2\2\2PQ\3\2\2\2Q_\5\30"+ - "\r\2RT\58\35\2SR\3\2\2\2ST\3\2\2\2TU\3\2\2\2U_\5\24\13\2VX\58\35\2WV\3"+ - "\2\2\2WX\3\2\2\2XY\3\2\2\2Y_\5\26\f\2Z\\\58\35\2[Z\3\2\2\2[\\\3\2\2\2"+ - "\\]\3\2\2\2]_\5\32\16\2^E\3\2\2\2^G\3\2\2\2^K\3\2\2\2^O\3\2\2\2^S\3\2"+ - "\2\2^W\3\2\2\2^[\3\2\2\2_\7\3\2\2\2`b\58\35\2a`\3\2\2\2ab\3\2\2\2bc\3"+ - "\2\2\2cd\7\36\2\2dg\7\'\2\2ef\7\n\2\2fh\5(\25\2ge\3\2\2\2gh\3\2\2\2hi"+ - "\3\2\2\2ij\7 \2\2jk\5\n\6\2k\t\3\2\2\2lm\b\6\1\2mn\7#\2\2ns\5\n\6\17o"+ - "s\5\20\t\2ps\5\f\7\2qs\5\16\b\2rl\3\2\2\2ro\3\2\2\2rp\3\2\2\2rq\3\2\2"+ - "\2s\u009a\3\2\2\2tu\f\16\2\2uv\7\6\2\2v\u0099\5\n\6\17wx\f\r\2\2xy\7\b"+ - "\2\2y\u0099\5\n\6\16z{\f\f\2\2{|\7\7\2\2|\u0099\5\n\6\r}~\f\13\2\2~\177"+ - "\7\23\2\2\177\u0099\5\n\6\f\u0080\u0081\f\n\2\2\u0081\u0082\7\13\2\2\u0082"+ - "\u0099\5\n\6\13\u0083\u0084\f\t\2\2\u0084\u0085\7\20\2\2\u0085\u0099\5"+ - "\n\6\n\u0086\u0087\f\b\2\2\u0087\u0088\7\16\2\2\u0088\u0099\5\n\6\t\u0089"+ - "\u008a\f\7\2\2\u008a\u008b\7\24\2\2\u008b\u0099\5\n\6\b\u008c\u008d\f"+ - "\6\2\2\u008d\u008e\7\22\2\2\u008e\u0099\5\n\6\7\u008f\u0090\f\5\2\2\u0090"+ - "\u0091\7\f\2\2\u0091\u0099\5\n\6\6\u0092\u0093\f\4\2\2\u0093\u0094\7!"+ - "\2\2\u0094\u0099\5\n\6\5\u0095\u0096\f\3\2\2\u0096\u0097\7\"\2\2\u0097"+ - "\u0099\5\n\6\4\u0098t\3\2\2\2\u0098w\3\2\2\2\u0098z\3\2\2\2\u0098}\3\2"+ - "\2\2\u0098\u0080\3\2\2\2\u0098\u0083\3\2\2\2\u0098\u0086\3\2\2\2\u0098"+ - "\u0089\3\2\2\2\u0098\u008c\3\2\2\2\u0098\u008f\3\2\2\2\u0098\u0092\3\2"+ - "\2\2\u0098\u0095\3\2\2\2\u0099\u009c\3\2\2\2\u009a\u0098\3\2\2\2\u009a"+ - "\u009b\3\2\2\2\u009b\13\3\2\2\2\u009c\u009a\3\2\2\2\u009d\u009e\5\20\t"+ - "\2\u009e\u00a0\7%\2\2\u009f\u00a1\5\n\6\2\u00a0\u009f\3\2\2\2\u00a0\u00a1"+ - "\3\2\2\2\u00a1\u00a2\3\2\2\2\u00a2\u00a3\7&\2\2\u00a3\r\3\2\2\2\u00a4"+ - "\u00aa\5\20\t\2\u00a5\u00a8\7\4\2\2\u00a6\u00a9\5,\27\2\u00a7\u00a9\5"+ - "\f\7\2\u00a8\u00a6\3\2\2\2\u00a8\u00a7\3\2\2\2\u00a9\u00ab\3\2\2\2\u00aa"+ - "\u00a5\3\2\2\2\u00ab\u00ac\3\2\2\2\u00ac\u00aa\3\2\2\2\u00ac\u00ad\3\2"+ - "\2\2\u00ad\17\3\2\2\2\u00ae\u00b0\58\35\2\u00af\u00ae\3\2\2\2\u00af\u00b0"+ - "\3\2\2\2\u00b0\u00b1\3\2\2\2\u00b1\u00c6\5*\26\2\u00b2\u00b4\58\35\2\u00b3"+ - "\u00b2\3\2\2\2\u00b3\u00b4\3\2\2\2\u00b4\u00b5\3\2\2\2\u00b5\u00c6\5,"+ - "\27\2\u00b6\u00b8\58\35\2\u00b7\u00b6\3\2\2\2\u00b7\u00b8\3\2\2\2\u00b8"+ - "\u00b9\3\2\2\2\u00b9\u00c6\5\22\n\2\u00ba\u00bc\58\35\2\u00bb\u00ba\3"+ - "\2\2\2\u00bb\u00bc\3\2\2\2\u00bc\u00bd\3\2\2\2\u00bd\u00c6\5\34\17\2\u00be"+ - "\u00c0\58\35\2\u00bf\u00be\3\2\2\2\u00bf\u00c0\3\2\2\2\u00c0\u00c1\3\2"+ - "\2\2\u00c1\u00c2\7%\2\2\u00c2\u00c3\5\n\6\2\u00c3\u00c4\7&\2\2\u00c4\u00c6"+ - "\3\2\2\2\u00c5\u00af\3\2\2\2\u00c5\u00b3\3\2\2\2\u00c5\u00b7\3\2\2\2\u00c5"+ - "\u00bb\3\2\2\2\u00c5\u00bf\3\2\2\2\u00c6\21\3\2\2\2\u00c7\u00c9\7\25\2"+ - "\2\u00c8\u00ca\5,\27\2\u00c9\u00c8\3\2\2\2\u00c9\u00ca\3\2\2\2\u00ca\u00cb"+ - "\3\2\2\2\u00cb\u00cd\7%\2\2\u00cc\u00ce\5\"\22\2\u00cd\u00cc\3\2\2\2\u00cd"+ - "\u00ce\3\2\2\2\u00ce\u00cf\3\2\2\2\u00cf\u00d2\7&\2\2\u00d0\u00d1\7\n"+ - "\2\2\u00d1\u00d3\5(\25\2\u00d2\u00d0\3\2\2\2\u00d2\u00d3\3\2\2\2\u00d3"+ - "\u00d4\3\2\2\2\u00d4\u00d5\7\32\2\2\u00d5\u00d6\5\4\3\2\u00d6\u00d7\7"+ - "\33\2\2\u00d7\u00e8\3\2\2\2\u00d8\u00da\7\25\2\2\u00d9\u00db\5,\27\2\u00da"+ - "\u00d9\3\2\2\2\u00da\u00db\3\2\2\2\u00db\u00dc\3\2\2\2\u00dc\u00de\7%"+ - "\2\2\u00dd\u00df\5\"\22\2\u00de\u00dd\3\2\2\2\u00de\u00df\3\2\2\2\u00df"+ - "\u00e0\3\2\2\2\u00e0\u00e3\7&\2\2\u00e1\u00e2\7\n\2\2\u00e2\u00e4\5(\25"+ - "\2\u00e3\u00e1\3\2\2\2\u00e3\u00e4\3\2\2\2\u00e4\u00e5\3\2\2\2\u00e5\u00e6"+ - "\7 \2\2\u00e6\u00e8\5\n\6\2\u00e7\u00c7\3\2\2\2\u00e7\u00d8\3\2\2\2\u00e8"+ - "\23\3\2\2\2\u00e9\u00ea\7\26\2\2\u00ea\u00ec\7%\2\2\u00eb\u00ed\7\36\2"+ - "\2\u00ec\u00eb\3\2\2\2\u00ec\u00ed\3\2\2\2\u00ed\u00ee\3\2\2\2\u00ee\u00f1"+ - "\5,\27\2\u00ef\u00f0\7\n\2\2\u00f0\u00f2\5(\25\2\u00f1\u00ef\3\2\2\2\u00f1"+ - "\u00f2\3\2\2\2\u00f2\u00f3\3\2\2\2\u00f3\u00f4\7\37\2\2\u00f4\u00f5\5"+ - "\n\6\2\u00f5\u00f6\7&\2\2\u00f6\u00f7\5\6\4\2\u00f7\25\3\2\2\2\u00f8\u00f9"+ - "\7\27\2\2\u00f9\u00fa\7%\2\2\u00fa\u00fb\5\n\6\2\u00fb\u00fc\7&\2\2\u00fc"+ - "\u00fd\5\6\4\2\u00fd\27\3\2\2\2\u00fe\u00ff\7\32\2\2\u00ff\u0100\5\4\3"+ - "\2\u0100\u0101\7\33\2\2\u0101\31\3\2\2\2\u0102\u0103\7\35\2\2\u0103\33"+ - "\3\2\2\2\u0104\u0105\5,\27\2\u0105\u0106\7 \2\2\u0106\u010f\5\n\6\2\u0107"+ - "\u0109\58\35\2\u0108\u0107\3\2\2\2\u0108\u0109\3\2\2\2\u0109\u010a\3\2"+ - "\2\2\u010a\u010b\7\30\2\2\u010b\u010c\7%\2\2\u010c\u010d\5\n\6\2\u010d"+ - "\u010e\7&\2\2\u010e\u0110\3\2\2\2\u010f\u0108\3\2\2\2\u010f\u0110\3\2"+ - "\2\2\u0110\35\3\2\2\2\u0111\u0112\7\30\2\2\u0112\u0113\7%\2\2\u0113\u0114"+ - "\5\n\6\2\u0114\u0115\7&\2\2\u0115\u0119\5\4\3\2\u0116\u0118\5 \21\2\u0117"+ - "\u0116\3\2\2\2\u0118\u011b\3\2\2\2\u0119\u0117\3\2\2\2\u0119\u011a\3\2"+ - "\2\2\u011a\u011e\3\2\2\2\u011b\u0119\3\2\2\2\u011c\u011d\7\31\2\2\u011d"+ - "\u011f\5\4\3\2\u011e\u011c\3\2\2\2\u011e\u011f\3\2\2\2\u011f\u0120\3\2"+ - "\2\2\u0120\u0121\7\33\2\2\u0121\37\3\2\2\2\u0122\u0123\7\31\2\2\u0123"+ - "\u0124\7\30\2\2\u0124\u0125\7%\2\2\u0125\u0126\5\n\6\2\u0126\u0127\7&"+ - "\2\2\u0127\u0128\5\4\3\2\u0128!\3\2\2\2\u0129\u012e\5$\23\2\u012a\u012b"+ - "\7\5\2\2\u012b\u012d\5$\23\2\u012c\u012a\3\2\2\2\u012d\u0130\3\2\2\2\u012e"+ - "\u012c\3\2\2\2\u012e\u012f\3\2\2\2\u012f#\3\2\2\2\u0130\u012e\3\2\2\2"+ - "\u0131\u0133\58\35\2\u0132\u0131\3\2\2\2\u0132\u0133\3\2\2\2\u0133\u0134"+ - "\3\2\2\2\u0134\u0135\5,\27\2\u0135\u0136\7\n\2\2\u0136\u0137\5(\25\2\u0137"+ - "%\3\2\2\2\u0138\u013d\5\n\6\2\u0139\u013a\7\5\2\2\u013a\u013c\5\n\6\2"+ - "\u013b\u0139\3\2\2\2\u013c\u013f\3\2\2\2\u013d\u013b\3\2\2\2\u013d\u013e"+ - "\3\2\2\2\u013e\'\3\2\2\2\u013f\u013d\3\2\2\2\u0140\u0142\58\35\2\u0141"+ - "\u0140\3\2\2\2\u0141\u0142\3\2\2\2\u0142\u0143\3\2\2\2\u0143\u0148\7\'"+ - "\2\2\u0144\u0145\7\4\2\2\u0145\u0147\7\'\2\2\u0146\u0144\3\2\2\2\u0147"+ - "\u014a\3\2\2\2\u0148\u0146\3\2\2\2\u0148\u0149\3\2\2\2\u0149)\3\2\2\2"+ - "\u014a\u0148\3\2\2\2\u014b\u0151\5.\30\2\u014c\u0151\5\60\31\2\u014d\u0151"+ - "\5\62\32\2\u014e\u0151\5\64\33\2\u014f\u0151\5\66\34\2\u0150\u014b\3\2"+ - "\2\2\u0150\u014c\3\2\2\2\u0150\u014d\3\2\2\2\u0150\u014e\3\2\2\2\u0150"+ - "\u014f\3\2\2\2\u0151+\3\2\2\2\u0152\u0153\7\'\2\2\u0153-\3\2\2\2\u0154"+ - "\u0155\7,\2\2\u0155/\3\2\2\2\u0156\u0157\7+\2\2\u0157\61\3\2\2\2\u0158"+ - "\u0159\7\34\2\2\u0159\63\3\2\2\2\u015a\u015b\7\t\2\2\u015b\u0160\5\n\6"+ - "\2\u015c\u015d\7\5\2\2\u015d\u015f\5\n\6\2\u015e\u015c\3\2\2\2\u015f\u0162"+ - "\3\2\2\2\u0160\u015e\3\2\2\2\u0160\u0161\3\2\2\2\u0161\u0163\3\2\2\2\u0162"+ - "\u0160\3\2\2\2\u0163\u0164\7\3\2\2\u0164\u0168\3\2\2\2\u0165\u0166\7\t"+ - "\2\2\u0166\u0168\7\3\2\2\u0167\u015a\3\2\2\2\u0167\u0165\3\2\2\2\u0168"+ - "\65\3\2\2\2\u0169\u016a\7,\2\2\u016a\u016b\7\17\2\2\u016b\u016e\7,\2\2"+ - "\u016c\u016d\7\21\2\2\u016d\u016f\7,\2\2\u016e\u016c\3\2\2\2\u016e\u016f"+ - "\3\2\2\2\u016f\67\3\2\2\2\u0170\u0172\5:\36\2\u0171\u0170\3\2\2\2\u0172"+ - "\u0173\3\2\2\2\u0173\u0171\3\2\2\2\u0173\u0174\3\2\2\2\u01749\3\2\2\2"+ - "\u0175\u0181\7*\2\2\u0176\u0177\7%\2\2\u0177\u017c\5*\26\2\u0178\u0179"+ - "\7\5\2\2\u0179\u017b\5*\26\2\u017a\u0178\3\2\2\2\u017b\u017e\3\2\2\2\u017c"+ - "\u017a\3\2\2\2\u017c\u017d\3\2\2\2\u017d\u017f\3\2\2\2\u017e\u017c\3\2"+ - "\2\2\u017f\u0180\7&\2\2\u0180\u0182\3\2\2\2\u0181\u0176\3\2\2\2\u0181"+ - "\u0182\3\2\2\2\u0182;\3\2\2\2\61CGKOSW[^agr\u0098\u009a\u00a0\u00a8\u00ac"+ - "\u00af\u00b3\u00b7\u00bb\u00bf\u00c5\u00c9\u00cd\u00d2\u00da\u00de\u00e3"+ - "\u00e7\u00ec\u00f1\u0108\u010f\u0119\u011e\u012e\u0132\u013d\u0141\u0148"+ - "\u0150\u0160\u0167\u016e\u0173\u017c\u0181"; + "\6\3\6\3\6\3\6\3\6\3\6\3\6\3\6\3\6\7\6\u009e\n\6\f\6\16\6\u00a1\13\6\3"+ + "\7\3\7\3\7\5\7\u00a6\n\7\3\7\3\7\3\b\3\b\5\b\u00ac\n\b\3\b\3\b\6\b\u00b0"+ + "\n\b\r\b\16\b\u00b1\3\t\3\t\5\t\u00b6\n\t\3\n\5\n\u00b9\n\n\3\n\3\n\5"+ + "\n\u00bd\n\n\3\n\3\n\5\n\u00c1\n\n\3\n\3\n\5\n\u00c5\n\n\3\n\3\n\5\n\u00c9"+ + "\n\n\3\n\3\n\3\n\3\n\5\n\u00cf\n\n\3\13\3\13\5\13\u00d3\n\13\3\13\3\13"+ + "\5\13\u00d7\n\13\3\13\3\13\3\13\3\13\3\13\3\13\3\13\3\13\3\13\3\13\5\13"+ + "\u00e3\n\13\3\13\3\13\5\13\u00e7\n\13\3\13\3\13\3\13\3\13\3\13\3\13\3"+ + "\13\5\13\u00f0\n\13\3\f\3\f\3\f\5\f\u00f5\n\f\3\f\3\f\3\f\5\f\u00fa\n"+ + "\f\3\f\3\f\3\f\3\f\3\f\3\r\3\r\3\r\3\r\3\r\3\r\3\16\3\16\3\16\3\16\3\17"+ + "\3\17\3\20\3\20\3\20\3\21\3\21\3\21\3\21\5\21\u0114\n\21\3\21\3\21\3\21"+ + "\3\21\3\21\5\21\u011b\n\21\3\22\3\22\3\22\3\22\3\22\3\22\7\22\u0123\n"+ + "\22\f\22\16\22\u0126\13\22\3\22\3\22\5\22\u012a\n\22\3\22\3\22\3\23\3"+ + "\23\3\23\3\23\3\23\3\23\3\23\3\24\3\24\3\24\7\24\u0138\n\24\f\24\16\24"+ + "\u013b\13\24\3\25\5\25\u013e\n\25\3\25\3\25\3\25\3\25\3\26\3\26\3\26\7"+ + "\26\u0147\n\26\f\26\16\26\u014a\13\26\3\27\5\27\u014d\n\27\3\27\3\27\3"+ + "\27\7\27\u0152\n\27\f\27\16\27\u0155\13\27\3\30\3\30\3\30\3\30\3\30\5"+ + "\30\u015c\n\30\3\31\3\31\3\32\3\32\3\33\3\33\3\34\3\34\3\35\3\35\3\35"+ + "\3\35\7\35\u016a\n\35\f\35\16\35\u016d\13\35\3\35\3\35\3\35\3\35\5\35"+ + "\u0173\n\35\3\36\3\36\3\36\3\36\3\36\5\36\u017a\n\36\3\37\6\37\u017d\n"+ + "\37\r\37\16\37\u017e\3 \3 \3 \3 \3 \7 \u0186\n \f \16 \u0189\13 \3 \3"+ + " \5 \u018d\n \3 \2!\2\4\6\b\n\f\16\20\22\24\26\30\32\34\36 \"$&(*,.\60"+ + "\62\64\668:<>\2\2\u01b5\2@\3\2\2\2\4E\3\2\2\2\6c\3\2\2\2\bf\3\2\2\2\n"+ + "w\3\2\2\2\f\u00a2\3\2\2\2\16\u00ab\3\2\2\2\20\u00b5\3\2\2\2\22\u00ce\3"+ + "\2\2\2\24\u00ef\3\2\2\2\26\u00f1\3\2\2\2\30\u0100\3\2\2\2\32\u0106\3\2"+ + "\2\2\34\u010a\3\2\2\2\36\u010c\3\2\2\2 \u010f\3\2\2\2\"\u011c\3\2\2\2"+ + "$\u012d\3\2\2\2&\u0134\3\2\2\2(\u013d\3\2\2\2*\u0143\3\2\2\2,\u014c\3"+ + "\2\2\2.\u015b\3\2\2\2\60\u015d\3\2\2\2\62\u015f\3\2\2\2\64\u0161\3\2\2"+ + "\2\66\u0163\3\2\2\28\u0172\3\2\2\2:\u0174\3\2\2\2<\u017c\3\2\2\2>\u0180"+ + "\3\2\2\2@A\5\4\3\2A\3\3\2\2\2BC\5\6\4\2CD\7\r\2\2DF\3\2\2\2EB\3\2\2\2"+ + "FG\3\2\2\2GE\3\2\2\2GH\3\2\2\2H\5\3\2\2\2Id\5\n\6\2Jd\5\36\20\2KM\5<\37"+ + "\2LK\3\2\2\2LM\3\2\2\2MN\3\2\2\2Nd\5\b\5\2OQ\5<\37\2PO\3\2\2\2PQ\3\2\2"+ + "\2QR\3\2\2\2Rd\5\"\22\2SU\5<\37\2TS\3\2\2\2TU\3\2\2\2UV\3\2\2\2Vd\5\32"+ + "\16\2WY\5<\37\2XW\3\2\2\2XY\3\2\2\2YZ\3\2\2\2Zd\5\26\f\2[]\5<\37\2\\["+ + "\3\2\2\2\\]\3\2\2\2]^\3\2\2\2^d\5\30\r\2_a\5<\37\2`_\3\2\2\2`a\3\2\2\2"+ + "ab\3\2\2\2bd\5\34\17\2cI\3\2\2\2cJ\3\2\2\2cL\3\2\2\2cP\3\2\2\2cT\3\2\2"+ + "\2cX\3\2\2\2c\\\3\2\2\2c`\3\2\2\2d\7\3\2\2\2eg\5<\37\2fe\3\2\2\2fg\3\2"+ + "\2\2gh\3\2\2\2hi\7\36\2\2il\7(\2\2jk\7\n\2\2km\5,\27\2lj\3\2\2\2lm\3\2"+ + "\2\2mn\3\2\2\2no\7 \2\2op\5\n\6\2p\t\3\2\2\2qr\b\6\1\2rs\7#\2\2sx\5\n"+ + "\6\2tx\5\22\n\2ux\5\f\7\2vx\5\16\b\2wq\3\2\2\2wt\3\2\2\2wu\3\2\2\2wv\3"+ + "\2\2\2x\u009f\3\2\2\2yz\6\6\2\3z{\7\6\2\2{\u009e\5\n\6\2|}\6\6\3\3}~\7"+ + "\b\2\2~\u009e\5\n\6\2\177\u0080\6\6\4\3\u0080\u0081\7\7\2\2\u0081\u009e"+ + "\5\n\6\2\u0082\u0083\6\6\5\3\u0083\u0084\7\23\2\2\u0084\u009e\5\n\6\2"+ + "\u0085\u0086\6\6\6\3\u0086\u0087\7\13\2\2\u0087\u009e\5\n\6\2\u0088\u0089"+ + "\6\6\7\3\u0089\u008a\7\20\2\2\u008a\u009e\5\n\6\2\u008b\u008c\6\6\b\3"+ + "\u008c\u008d\7\16\2\2\u008d\u009e\5\n\6\2\u008e\u008f\6\6\t\3\u008f\u0090"+ + "\7\24\2\2\u0090\u009e\5\n\6\2\u0091\u0092\6\6\n\3\u0092\u0093\7\22\2\2"+ + "\u0093\u009e\5\n\6\2\u0094\u0095\6\6\13\3\u0095\u0096\7\f\2\2\u0096\u009e"+ + "\5\n\6\2\u0097\u0098\6\6\f\3\u0098\u0099\7!\2\2\u0099\u009e\5\n\6\2\u009a"+ + "\u009b\6\6\r\3\u009b\u009c\7\"\2\2\u009c\u009e\5\n\6\2\u009dy\3\2\2\2"+ + "\u009d|\3\2\2\2\u009d\177\3\2\2\2\u009d\u0082\3\2\2\2\u009d\u0085\3\2"+ + "\2\2\u009d\u0088\3\2\2\2\u009d\u008b\3\2\2\2\u009d\u008e\3\2\2\2\u009d"+ + "\u0091\3\2\2\2\u009d\u0094\3\2\2\2\u009d\u0097\3\2\2\2\u009d\u009a\3\2"+ + "\2\2\u009e\u00a1\3\2\2\2\u009f\u009d\3\2\2\2\u009f\u00a0\3\2\2\2\u00a0"+ + "\13\3\2\2\2\u00a1\u009f\3\2\2\2\u00a2\u00a3\5\22\n\2\u00a3\u00a5\7&\2"+ + "\2\u00a4\u00a6\5\n\6\2\u00a5\u00a4\3\2\2\2\u00a5\u00a6\3\2\2\2\u00a6\u00a7"+ + "\3\2\2\2\u00a7\u00a8\7\'\2\2\u00a8\r\3\2\2\2\u00a9\u00ac\5\22\n\2\u00aa"+ + "\u00ac\5\f\7\2\u00ab\u00a9\3\2\2\2\u00ab\u00aa\3\2\2\2\u00ac\u00af\3\2"+ + "\2\2\u00ad\u00ae\7\4\2\2\u00ae\u00b0\5\20\t\2\u00af\u00ad\3\2\2\2\u00b0"+ + "\u00b1\3\2\2\2\u00b1\u00af\3\2\2\2\u00b1\u00b2\3\2\2\2\u00b2\17\3\2\2"+ + "\2\u00b3\u00b6\5\60\31\2\u00b4\u00b6\5\f\7\2\u00b5\u00b3\3\2\2\2\u00b5"+ + "\u00b4\3\2\2\2\u00b6\21\3\2\2\2\u00b7\u00b9\5<\37\2\u00b8\u00b7\3\2\2"+ + "\2\u00b8\u00b9\3\2\2\2\u00b9\u00ba\3\2\2\2\u00ba\u00cf\5.\30\2\u00bb\u00bd"+ + "\5<\37\2\u00bc\u00bb\3\2\2\2\u00bc\u00bd\3\2\2\2\u00bd\u00be\3\2\2\2\u00be"+ + "\u00cf\7(\2\2\u00bf\u00c1\5<\37\2\u00c0\u00bf\3\2\2\2\u00c0\u00c1\3\2"+ + "\2\2\u00c1\u00c2\3\2\2\2\u00c2\u00cf\5\24\13\2\u00c3\u00c5\5<\37\2\u00c4"+ + "\u00c3\3\2\2\2\u00c4\u00c5\3\2\2\2\u00c5\u00c6\3\2\2\2\u00c6\u00cf\5 "+ + "\21\2\u00c7\u00c9\5<\37\2\u00c8\u00c7\3\2\2\2\u00c8\u00c9\3\2\2\2\u00c9"+ + "\u00ca\3\2\2\2\u00ca\u00cb\7&\2\2\u00cb\u00cc\5\n\6\2\u00cc\u00cd\7\'"+ + "\2\2\u00cd\u00cf\3\2\2\2\u00ce\u00b8\3\2\2\2\u00ce\u00bc\3\2\2\2\u00ce"+ + "\u00c0\3\2\2\2\u00ce\u00c4\3\2\2\2\u00ce\u00c8\3\2\2\2\u00cf\23\3\2\2"+ + "\2\u00d0\u00d2\7\25\2\2\u00d1\u00d3\7(\2\2\u00d2\u00d1\3\2\2\2\u00d2\u00d3"+ + "\3\2\2\2\u00d3\u00d4\3\2\2\2\u00d4\u00d6\7&\2\2\u00d5\u00d7\5&\24\2\u00d6"+ + "\u00d5\3\2\2\2\u00d6\u00d7\3\2\2\2\u00d7\u00d8\3\2\2\2\u00d8\u00d9\7\'"+ + "\2\2\u00d9\u00da\7\n\2\2\u00da\u00db\5,\27\2\u00db\u00dc\3\2\2\2\u00dc"+ + "\u00dd\7\32\2\2\u00dd\u00de\5\4\3\2\u00de\u00df\7\33\2\2\u00df\u00f0\3"+ + "\2\2\2\u00e0\u00e2\7\25\2\2\u00e1\u00e3\7(\2\2\u00e2\u00e1\3\2\2\2\u00e2"+ + "\u00e3\3\2\2\2\u00e3\u00e4\3\2\2\2\u00e4\u00e6\7&\2\2\u00e5\u00e7\5&\24"+ + "\2\u00e6\u00e5\3\2\2\2\u00e6\u00e7\3\2\2\2\u00e7\u00e8\3\2\2\2\u00e8\u00e9"+ + "\7\'\2\2\u00e9\u00ea\7\n\2\2\u00ea\u00eb\5,\27\2\u00eb\u00ec\3\2\2\2\u00ec"+ + "\u00ed\7 \2\2\u00ed\u00ee\5\n\6\2\u00ee\u00f0\3\2\2\2\u00ef\u00d0\3\2"+ + "\2\2\u00ef\u00e0\3\2\2\2\u00f0\25\3\2\2\2\u00f1\u00f2\7\26\2\2\u00f2\u00f4"+ + "\7&\2\2\u00f3\u00f5\7\36\2\2\u00f4\u00f3\3\2\2\2\u00f4\u00f5\3\2\2\2\u00f5"+ + "\u00f6\3\2\2\2\u00f6\u00f9\7(\2\2\u00f7\u00f8\7\n\2\2\u00f8\u00fa\5,\27"+ + "\2\u00f9\u00f7\3\2\2\2\u00f9\u00fa\3\2\2\2\u00fa\u00fb\3\2\2\2\u00fb\u00fc"+ + "\7\37\2\2\u00fc\u00fd\5\n\6\2\u00fd\u00fe\7\'\2\2\u00fe\u00ff\5\6\4\2"+ + "\u00ff\27\3\2\2\2\u0100\u0101\7\27\2\2\u0101\u0102\7&\2\2\u0102\u0103"+ + "\5\n\6\2\u0103\u0104\7\'\2\2\u0104\u0105\5\6\4\2\u0105\31\3\2\2\2\u0106"+ + "\u0107\7\32\2\2\u0107\u0108\5\4\3\2\u0108\u0109\7\33\2\2\u0109\33\3\2"+ + "\2\2\u010a\u010b\7\35\2\2\u010b\35\3\2\2\2\u010c\u010d\7%\2\2\u010d\u010e"+ + "\5\n\6\2\u010e\37\3\2\2\2\u010f\u0110\7(\2\2\u0110\u0111\7 \2\2\u0111"+ + "\u011a\5\n\6\2\u0112\u0114\5<\37\2\u0113\u0112\3\2\2\2\u0113\u0114\3\2"+ + "\2\2\u0114\u0115\3\2\2\2\u0115\u0116\7\30\2\2\u0116\u0117\7&\2\2\u0117"+ + "\u0118\5\n\6\2\u0118\u0119\7\'\2\2\u0119\u011b\3\2\2\2\u011a\u0113\3\2"+ + "\2\2\u011a\u011b\3\2\2\2\u011b!\3\2\2\2\u011c\u011d\7\30\2\2\u011d\u011e"+ + "\7&\2\2\u011e\u011f\5\n\6\2\u011f\u0120\7\'\2\2\u0120\u0124\5\4\3\2\u0121"+ + "\u0123\5$\23\2\u0122\u0121\3\2\2\2\u0123\u0126\3\2\2\2\u0124\u0122\3\2"+ + "\2\2\u0124\u0125\3\2\2\2\u0125\u0129\3\2\2\2\u0126\u0124\3\2\2\2\u0127"+ + "\u0128\7\31\2\2\u0128\u012a\5\4\3\2\u0129\u0127\3\2\2\2\u0129\u012a\3"+ + "\2\2\2\u012a\u012b\3\2\2\2\u012b\u012c\7\33\2\2\u012c#\3\2\2\2\u012d\u012e"+ + "\7\31\2\2\u012e\u012f\7\30\2\2\u012f\u0130\7&\2\2\u0130\u0131\5\n\6\2"+ + "\u0131\u0132\7\'\2\2\u0132\u0133\5\4\3\2\u0133%\3\2\2\2\u0134\u0139\5"+ + "(\25\2\u0135\u0136\7\5\2\2\u0136\u0138\5(\25\2\u0137\u0135\3\2\2\2\u0138"+ + "\u013b\3\2\2\2\u0139\u0137\3\2\2\2\u0139\u013a\3\2\2\2\u013a\'\3\2\2\2"+ + "\u013b\u0139\3\2\2\2\u013c\u013e\5<\37\2\u013d\u013c\3\2\2\2\u013d\u013e"+ + "\3\2\2\2\u013e\u013f\3\2\2\2\u013f\u0140\7(\2\2\u0140\u0141\7\n\2\2\u0141"+ + "\u0142\5,\27\2\u0142)\3\2\2\2\u0143\u0148\5\n\6\2\u0144\u0145\7\5\2\2"+ + "\u0145\u0147\5\n\6\2\u0146\u0144\3\2\2\2\u0147\u014a\3\2\2\2\u0148\u0146"+ + "\3\2\2\2\u0148\u0149\3\2\2\2\u0149+\3\2\2\2\u014a\u0148\3\2\2\2\u014b"+ + "\u014d\5<\37\2\u014c\u014b\3\2\2\2\u014c\u014d\3\2\2\2\u014d\u014e\3\2"+ + "\2\2\u014e\u0153\7(\2\2\u014f\u0150\7\4\2\2\u0150\u0152\7(\2\2\u0151\u014f"+ + "\3\2\2\2\u0152\u0155\3\2\2\2\u0153\u0151\3\2\2\2\u0153\u0154\3\2\2\2\u0154"+ + "-\3\2\2\2\u0155\u0153\3\2\2\2\u0156\u015c\5\62\32\2\u0157\u015c\5\64\33"+ + "\2\u0158\u015c\5\66\34\2\u0159\u015c\58\35\2\u015a\u015c\5:\36\2\u015b"+ + "\u0156\3\2\2\2\u015b\u0157\3\2\2\2\u015b\u0158\3\2\2\2\u015b\u0159\3\2"+ + "\2\2\u015b\u015a\3\2\2\2\u015c/\3\2\2\2\u015d\u015e\7(\2\2\u015e\61\3"+ + "\2\2\2\u015f\u0160\7-\2\2\u0160\63\3\2\2\2\u0161\u0162\7,\2\2\u0162\65"+ + "\3\2\2\2\u0163\u0164\7\34\2\2\u0164\67\3\2\2\2\u0165\u0166\7\t\2\2\u0166"+ + "\u016b\5\n\6\2\u0167\u0168\7\5\2\2\u0168\u016a\5\n\6\2\u0169\u0167\3\2"+ + "\2\2\u016a\u016d\3\2\2\2\u016b\u0169\3\2\2\2\u016b\u016c\3\2\2\2\u016c"+ + "\u016e\3\2\2\2\u016d\u016b\3\2\2\2\u016e\u016f\7\3\2\2\u016f\u0173\3\2"+ + "\2\2\u0170\u0171\7\t\2\2\u0171\u0173\7\3\2\2\u0172\u0165\3\2\2\2\u0172"+ + "\u0170\3\2\2\2\u01739\3\2\2\2\u0174\u0175\7-\2\2\u0175\u0176\7\17\2\2"+ + "\u0176\u0179\7-\2\2\u0177\u0178\7\21\2\2\u0178\u017a\7-\2\2\u0179\u0177"+ + "\3\2\2\2\u0179\u017a\3\2\2\2\u017a;\3\2\2\2\u017b\u017d\5> \2\u017c\u017b"+ + "\3\2\2\2\u017d\u017e\3\2\2\2\u017e\u017c\3\2\2\2\u017e\u017f\3\2\2\2\u017f"+ + "=\3\2\2\2\u0180\u018c\7+\2\2\u0181\u0182\7&\2\2\u0182\u0187\5.\30\2\u0183"+ + "\u0184\7\5\2\2\u0184\u0186\5.\30\2\u0185\u0183\3\2\2\2\u0186\u0189\3\2"+ + "\2\2\u0187\u0185\3\2\2\2\u0187\u0188\3\2\2\2\u0188\u018a\3\2\2\2\u0189"+ + "\u0187\3\2\2\2\u018a\u018b\7\'\2\2\u018b\u018d\3\2\2\2\u018c\u0181\3\2"+ + "\2\2\u018c\u018d\3\2\2\2\u018d?\3\2\2\2\60GLPTX\\`cflw\u009d\u009f\u00a5"+ + "\u00ab\u00b1\u00b5\u00b8\u00bc\u00c0\u00c4\u00c8\u00ce\u00d2\u00d6\u00e2"+ + "\u00e6\u00ef\u00f4\u00f9\u0113\u011a\u0124\u0129\u0139\u013d\u0148\u014c"+ + "\u0153\u015b\u016b\u0172\u0179\u017e\u0187\u018c"; public static final ATN _ATN = - new ATNDeserializer().deserialize(_serializedATN.toCharArray()); + ATNSimulator.deserialize(_serializedATN.toCharArray()); static { _decisionToDFA = new DFA[_ATN.getNumberOfDecisions()]; for (int i = 0; i < _ATN.getNumberOfDecisions(); i++) { diff --git a/grammar/src/MetaCodeVisitor.class b/grammar/src/MetaCodeVisitor.class index cd5f9b1..293969a 100644 Binary files a/grammar/src/MetaCodeVisitor.class and b/grammar/src/MetaCodeVisitor.class differ diff --git a/grammar/src/MetaCodeVisitor.java b/grammar/src/MetaCodeVisitor.java index 2c6ab34..0f4e23c 100644 --- a/grammar/src/MetaCodeVisitor.java +++ b/grammar/src/MetaCodeVisitor.java @@ -1,4 +1,4 @@ -// Generated from ../MetaCode.g4 by ANTLR 4.2 +// Generated from ../MetaCode.g4 by ANTLR 4.1 import org.antlr.v4.runtime.misc.NotNull; import org.antlr.v4.runtime.tree.ParseTreeVisitor; @@ -24,6 +24,13 @@ public interface MetaCodeVisitor extends ParseTreeVisitor { */ T visitFormalParameter(@NotNull MetaCodeParser.FormalParameterContext ctx); + /** + * Visit a parse tree produced by {@link MetaCodeParser#returnStatement}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitReturnStatement(@NotNull MetaCodeParser.ReturnStatementContext ctx); + /** * Visit a parse tree produced by {@link MetaCodeParser#attribute}. * @param ctx the parse tree @@ -115,6 +122,13 @@ public interface MetaCodeVisitor extends ParseTreeVisitor { */ T visitBooleanConstant(@NotNull MetaCodeParser.BooleanConstantContext ctx); + /** + * Visit a parse tree produced by {@link MetaCodeParser#memberTagExpression}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitMemberTagExpression(@NotNull MetaCodeParser.MemberTagExpressionContext ctx); + /** * Visit a parse tree produced by {@link MetaCodeParser#assignmentExpression}. * @param ctx the parse tree @@ -200,16 +214,16 @@ public interface MetaCodeVisitor extends ParseTreeVisitor { T visitForeachStatement(@NotNull MetaCodeParser.ForeachStatementContext ctx); /** - * Visit a parse tree produced by {@link MetaCodeParser#identifier}. + * Visit a parse tree produced by {@link MetaCodeParser#stringConstant}. * @param ctx the parse tree * @return the visitor result */ - T visitIdentifier(@NotNull MetaCodeParser.IdentifierContext ctx); + T visitStringConstant(@NotNull MetaCodeParser.StringConstantContext ctx); /** - * Visit a parse tree produced by {@link MetaCodeParser#stringConstant}. + * Visit a parse tree produced by {@link MetaCodeParser#identifier}. * @param ctx the parse tree * @return the visitor result */ - T visitStringConstant(@NotNull MetaCodeParser.StringConstantContext ctx); + T visitIdentifier(@NotNull MetaCodeParser.IdentifierContext ctx); } \ No newline at end of file diff --git a/project/MetaCode/MetaCode.CodeVisualizer/App.config b/project/MetaCode/MetaCode.CodeVisualizer/App.config new file mode 100644 index 0000000..8e15646 --- /dev/null +++ b/project/MetaCode/MetaCode.CodeVisualizer/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/project/MetaCode/MetaCode.CodeVisualizer/App.xaml b/project/MetaCode/MetaCode.CodeVisualizer/App.xaml new file mode 100644 index 0000000..e7bca29 --- /dev/null +++ b/project/MetaCode/MetaCode.CodeVisualizer/App.xaml @@ -0,0 +1,8 @@ + + + + + diff --git a/project/MetaCode/MetaCode.CodeVisualizer/App.xaml.cs b/project/MetaCode/MetaCode.CodeVisualizer/App.xaml.cs new file mode 100644 index 0000000..f16f7aa --- /dev/null +++ b/project/MetaCode/MetaCode.CodeVisualizer/App.xaml.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Data; +using System.Linq; +using System.Threading.Tasks; +using System.Windows; + +namespace MetaCode.CodeVisualizer +{ + ///

+ /// Interaction logic for App.xaml + /// + public partial class App : Application + { + } +} diff --git a/project/MetaCode/MetaCode.CodeVisualizer/MainWindow.xaml b/project/MetaCode/MetaCode.CodeVisualizer/MainWindow.xaml new file mode 100644 index 0000000..c55b04d --- /dev/null +++ b/project/MetaCode/MetaCode.CodeVisualizer/MainWindow.xaml @@ -0,0 +1,8 @@ + + + + + diff --git a/project/MetaCode/MetaCode.CodeVisualizer/MainWindow.xaml.cs b/project/MetaCode/MetaCode.CodeVisualizer/MainWindow.xaml.cs new file mode 100644 index 0000000..d79a11c --- /dev/null +++ b/project/MetaCode/MetaCode.CodeVisualizer/MainWindow.xaml.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace MetaCode.CodeVisualizer +{ + /// + /// Interaction logic for MainWindow.xaml + /// + public partial class MainWindow : Window + { + public MainWindow() + { + InitializeComponent(); + } + } +} diff --git a/project/MetaCode/MetaCode.CodeVisualizer/MetaCode.CodeVisualizer.csproj b/project/MetaCode/MetaCode.CodeVisualizer/MetaCode.CodeVisualizer.csproj new file mode 100644 index 0000000..78df91f --- /dev/null +++ b/project/MetaCode/MetaCode.CodeVisualizer/MetaCode.CodeVisualizer.csproj @@ -0,0 +1,104 @@ + + + + + Debug + AnyCPU + {8FA5FC15-2A2D-4DCD-A8CD-8664B0EFCBAA} + WinExe + Properties + MetaCode.CodeVisualizer + MetaCode.CodeVisualizer + v4.5 + 512 + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 4 + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + 4.0 + + + + + + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + App.xaml + Code + + + MainWindow.xaml + Code + + + + + Code + + + True + True + Resources.resx + + + True + Settings.settings + True + + + ResXFileCodeGenerator + Resources.Designer.cs + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + + \ No newline at end of file diff --git a/project/MetaCode/MetaCode.CodeVisualizer/Properties/AssemblyInfo.cs b/project/MetaCode/MetaCode.CodeVisualizer/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..1121390 --- /dev/null +++ b/project/MetaCode/MetaCode.CodeVisualizer/Properties/AssemblyInfo.cs @@ -0,0 +1,55 @@ +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Windows; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("MetaCode.CodeVisualizer")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("MetaCode.CodeVisualizer")] +[assembly: AssemblyCopyright("Copyright © 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +//In order to begin building localizable applications, set +//CultureYouAreCodingWith in your .csproj file +//inside a . For example, if you are using US english +//in your source files, set the to en-US. Then uncomment +//the NeutralResourceLanguage attribute below. Update the "en-US" in +//the line below to match the UICulture setting in the project file. + +//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] + + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] + + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/project/MetaCode/MetaCode.CodeVisualizer/Properties/Resources.Designer.cs b/project/MetaCode/MetaCode.CodeVisualizer/Properties/Resources.Designer.cs new file mode 100644 index 0000000..299f290 --- /dev/null +++ b/project/MetaCode/MetaCode.CodeVisualizer/Properties/Resources.Designer.cs @@ -0,0 +1,71 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.34011 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace MetaCode.CodeVisualizer.Properties +{ + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources + { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() + { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager + { + get + { + if ((resourceMan == null)) + { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("MetaCode.CodeVisualizer.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture + { + get + { + return resourceCulture; + } + set + { + resourceCulture = value; + } + } + } +} diff --git a/project/MetaCode/MetaCode.CodeVisualizer/Properties/Resources.resx b/project/MetaCode/MetaCode.CodeVisualizer/Properties/Resources.resx new file mode 100644 index 0000000..af7dbeb --- /dev/null +++ b/project/MetaCode/MetaCode.CodeVisualizer/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/project/MetaCode/MetaCode.CodeVisualizer/Properties/Settings.Designer.cs b/project/MetaCode/MetaCode.CodeVisualizer/Properties/Settings.Designer.cs new file mode 100644 index 0000000..d8ad048 --- /dev/null +++ b/project/MetaCode/MetaCode.CodeVisualizer/Properties/Settings.Designer.cs @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.34011 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace MetaCode.CodeVisualizer.Properties +{ + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase + { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default + { + get + { + return defaultInstance; + } + } + } +} diff --git a/project/MetaCode/MetaCode.CodeVisualizer/Properties/Settings.settings b/project/MetaCode/MetaCode.CodeVisualizer/Properties/Settings.settings new file mode 100644 index 0000000..033d7a5 --- /dev/null +++ b/project/MetaCode/MetaCode.CodeVisualizer/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/project/MetaCode/MetaCode.CodeVisualizer/bin/Debug/MetaCode.CodeVisualizer.exe b/project/MetaCode/MetaCode.CodeVisualizer/bin/Debug/MetaCode.CodeVisualizer.exe new file mode 100644 index 0000000..3c44ee8 Binary files /dev/null and b/project/MetaCode/MetaCode.CodeVisualizer/bin/Debug/MetaCode.CodeVisualizer.exe differ diff --git a/project/MetaCode/MetaCode.CodeVisualizer/bin/Debug/MetaCode.CodeVisualizer.exe.config b/project/MetaCode/MetaCode.CodeVisualizer/bin/Debug/MetaCode.CodeVisualizer.exe.config new file mode 100644 index 0000000..8e15646 --- /dev/null +++ b/project/MetaCode/MetaCode.CodeVisualizer/bin/Debug/MetaCode.CodeVisualizer.exe.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/project/MetaCode/MetaCode.CodeVisualizer/bin/Debug/MetaCode.CodeVisualizer.pdb b/project/MetaCode/MetaCode.CodeVisualizer/bin/Debug/MetaCode.CodeVisualizer.pdb new file mode 100644 index 0000000..064f634 Binary files /dev/null and b/project/MetaCode/MetaCode.CodeVisualizer/bin/Debug/MetaCode.CodeVisualizer.pdb differ diff --git a/project/MetaCode/MetaCode.CodeVisualizer/obj/Debug/App.g.cs b/project/MetaCode/MetaCode.CodeVisualizer/obj/Debug/App.g.cs new file mode 100644 index 0000000..62d2f6f --- /dev/null +++ b/project/MetaCode/MetaCode.CodeVisualizer/obj/Debug/App.g.cs @@ -0,0 +1,69 @@ +#pragma checksum "..\..\App.xaml" "{406ea660-64cf-4c82-b6f0-42d48172a799}" "2F6E2EF1A3D0A62D186D715DC0D954CF" +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.34011 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Diagnostics; +using System.Windows; +using System.Windows.Automation; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Ink; +using System.Windows.Input; +using System.Windows.Markup; +using System.Windows.Media; +using System.Windows.Media.Animation; +using System.Windows.Media.Effects; +using System.Windows.Media.Imaging; +using System.Windows.Media.Media3D; +using System.Windows.Media.TextFormatting; +using System.Windows.Navigation; +using System.Windows.Shapes; +using System.Windows.Shell; + + +namespace MetaCode.CodeVisualizer { + + + /// + /// App + /// + public partial class App : System.Windows.Application { + + /// + /// InitializeComponent + /// + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + [System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "4.0.0.0")] + public void InitializeComponent() { + + #line 4 "..\..\App.xaml" + this.StartupUri = new System.Uri("MainWindow.xaml", System.UriKind.Relative); + + #line default + #line hidden + } + + /// + /// Application Entry Point. + /// + [System.STAThreadAttribute()] + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + [System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "4.0.0.0")] + public static void Main() { + MetaCode.CodeVisualizer.App app = new MetaCode.CodeVisualizer.App(); + app.InitializeComponent(); + app.Run(); + } + } +} + diff --git a/project/MetaCode/MetaCode.CodeVisualizer/obj/Debug/App.g.i.cs b/project/MetaCode/MetaCode.CodeVisualizer/obj/Debug/App.g.i.cs new file mode 100644 index 0000000..62d2f6f --- /dev/null +++ b/project/MetaCode/MetaCode.CodeVisualizer/obj/Debug/App.g.i.cs @@ -0,0 +1,69 @@ +#pragma checksum "..\..\App.xaml" "{406ea660-64cf-4c82-b6f0-42d48172a799}" "2F6E2EF1A3D0A62D186D715DC0D954CF" +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.34011 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Diagnostics; +using System.Windows; +using System.Windows.Automation; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Ink; +using System.Windows.Input; +using System.Windows.Markup; +using System.Windows.Media; +using System.Windows.Media.Animation; +using System.Windows.Media.Effects; +using System.Windows.Media.Imaging; +using System.Windows.Media.Media3D; +using System.Windows.Media.TextFormatting; +using System.Windows.Navigation; +using System.Windows.Shapes; +using System.Windows.Shell; + + +namespace MetaCode.CodeVisualizer { + + + /// + /// App + /// + public partial class App : System.Windows.Application { + + /// + /// InitializeComponent + /// + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + [System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "4.0.0.0")] + public void InitializeComponent() { + + #line 4 "..\..\App.xaml" + this.StartupUri = new System.Uri("MainWindow.xaml", System.UriKind.Relative); + + #line default + #line hidden + } + + /// + /// Application Entry Point. + /// + [System.STAThreadAttribute()] + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + [System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "4.0.0.0")] + public static void Main() { + MetaCode.CodeVisualizer.App app = new MetaCode.CodeVisualizer.App(); + app.InitializeComponent(); + app.Run(); + } + } +} + diff --git a/project/MetaCode/MetaCode.CodeVisualizer/obj/Debug/DesignTimeResolveAssemblyReferencesInput.cache b/project/MetaCode/MetaCode.CodeVisualizer/obj/Debug/DesignTimeResolveAssemblyReferencesInput.cache new file mode 100644 index 0000000..628f3ec Binary files /dev/null and b/project/MetaCode/MetaCode.CodeVisualizer/obj/Debug/DesignTimeResolveAssemblyReferencesInput.cache differ diff --git a/project/MetaCode/MetaCode.CodeVisualizer/obj/Debug/MainWindow.baml b/project/MetaCode/MetaCode.CodeVisualizer/obj/Debug/MainWindow.baml new file mode 100644 index 0000000..cbf9f3e Binary files /dev/null and b/project/MetaCode/MetaCode.CodeVisualizer/obj/Debug/MainWindow.baml differ diff --git a/project/MetaCode/MetaCode.CodeVisualizer/obj/Debug/MainWindow.g.cs b/project/MetaCode/MetaCode.CodeVisualizer/obj/Debug/MainWindow.g.cs new file mode 100644 index 0000000..a17c2b0 --- /dev/null +++ b/project/MetaCode/MetaCode.CodeVisualizer/obj/Debug/MainWindow.g.cs @@ -0,0 +1,74 @@ +#pragma checksum "..\..\MainWindow.xaml" "{406ea660-64cf-4c82-b6f0-42d48172a799}" "BFB6842B1B82659611354009AF43D134" +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.34011 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Diagnostics; +using System.Windows; +using System.Windows.Automation; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Ink; +using System.Windows.Input; +using System.Windows.Markup; +using System.Windows.Media; +using System.Windows.Media.Animation; +using System.Windows.Media.Effects; +using System.Windows.Media.Imaging; +using System.Windows.Media.Media3D; +using System.Windows.Media.TextFormatting; +using System.Windows.Navigation; +using System.Windows.Shapes; +using System.Windows.Shell; + + +namespace MetaCode.CodeVisualizer { + + + /// + /// MainWindow + /// + public partial class MainWindow : System.Windows.Window, System.Windows.Markup.IComponentConnector { + + private bool _contentLoaded; + + /// + /// InitializeComponent + /// + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + [System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "4.0.0.0")] + public void InitializeComponent() { + if (_contentLoaded) { + return; + } + _contentLoaded = true; + System.Uri resourceLocater = new System.Uri("/MetaCode.CodeVisualizer;component/mainwindow.xaml", System.UriKind.Relative); + + #line 1 "..\..\MainWindow.xaml" + System.Windows.Application.LoadComponent(this, resourceLocater); + + #line default + #line hidden + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + [System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "4.0.0.0")] + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes")] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily")] + void System.Windows.Markup.IComponentConnector.Connect(int connectionId, object target) { + this._contentLoaded = true; + } + } +} + diff --git a/project/MetaCode/MetaCode.CodeVisualizer/obj/Debug/MainWindow.g.i.cs b/project/MetaCode/MetaCode.CodeVisualizer/obj/Debug/MainWindow.g.i.cs new file mode 100644 index 0000000..a17c2b0 --- /dev/null +++ b/project/MetaCode/MetaCode.CodeVisualizer/obj/Debug/MainWindow.g.i.cs @@ -0,0 +1,74 @@ +#pragma checksum "..\..\MainWindow.xaml" "{406ea660-64cf-4c82-b6f0-42d48172a799}" "BFB6842B1B82659611354009AF43D134" +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.34011 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Diagnostics; +using System.Windows; +using System.Windows.Automation; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Ink; +using System.Windows.Input; +using System.Windows.Markup; +using System.Windows.Media; +using System.Windows.Media.Animation; +using System.Windows.Media.Effects; +using System.Windows.Media.Imaging; +using System.Windows.Media.Media3D; +using System.Windows.Media.TextFormatting; +using System.Windows.Navigation; +using System.Windows.Shapes; +using System.Windows.Shell; + + +namespace MetaCode.CodeVisualizer { + + + /// + /// MainWindow + /// + public partial class MainWindow : System.Windows.Window, System.Windows.Markup.IComponentConnector { + + private bool _contentLoaded; + + /// + /// InitializeComponent + /// + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + [System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "4.0.0.0")] + public void InitializeComponent() { + if (_contentLoaded) { + return; + } + _contentLoaded = true; + System.Uri resourceLocater = new System.Uri("/MetaCode.CodeVisualizer;component/mainwindow.xaml", System.UriKind.Relative); + + #line 1 "..\..\MainWindow.xaml" + System.Windows.Application.LoadComponent(this, resourceLocater); + + #line default + #line hidden + } + + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + [System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "4.0.0.0")] + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes")] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily")] + void System.Windows.Markup.IComponentConnector.Connect(int connectionId, object target) { + this._contentLoaded = true; + } + } +} + diff --git a/project/MetaCode/MetaCode.CodeVisualizer/obj/Debug/MetaCode.CodeVisualizer.Properties.Resources.resources b/project/MetaCode/MetaCode.CodeVisualizer/obj/Debug/MetaCode.CodeVisualizer.Properties.Resources.resources new file mode 100644 index 0000000..6c05a97 Binary files /dev/null and b/project/MetaCode/MetaCode.CodeVisualizer/obj/Debug/MetaCode.CodeVisualizer.Properties.Resources.resources differ diff --git a/project/MetaCode/MetaCode.CodeVisualizer/obj/Debug/MetaCode.CodeVisualizer.csproj.FileListAbsolute.txt b/project/MetaCode/MetaCode.CodeVisualizer/obj/Debug/MetaCode.CodeVisualizer.csproj.FileListAbsolute.txt new file mode 100644 index 0000000..9116467 --- /dev/null +++ b/project/MetaCode/MetaCode.CodeVisualizer/obj/Debug/MetaCode.CodeVisualizer.csproj.FileListAbsolute.txt @@ -0,0 +1,13 @@ +E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.CodeVisualizer\bin\Debug\MetaCode.CodeVisualizer.exe.config +E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.CodeVisualizer\bin\Debug\MetaCode.CodeVisualizer.exe +E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.CodeVisualizer\bin\Debug\MetaCode.CodeVisualizer.pdb +E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.CodeVisualizer\obj\Debug\MetaCode.CodeVisualizer.csprojResolveAssemblyReference.cache +E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.CodeVisualizer\obj\Debug\MainWindow.baml +E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.CodeVisualizer\obj\Debug\MainWindow.g.cs +E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.CodeVisualizer\obj\Debug\App.g.cs +E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.CodeVisualizer\obj\Debug\MetaCode.CodeVisualizer_MarkupCompile.cache +E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.CodeVisualizer\obj\Debug\MetaCode.CodeVisualizer.g.resources +E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.CodeVisualizer\obj\Debug\MetaCode.CodeVisualizer.Properties.Resources.resources +E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.CodeVisualizer\obj\Debug\MetaCode.CodeVisualizer.csproj.GenerateResource.Cache +E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.CodeVisualizer\obj\Debug\MetaCode.CodeVisualizer.exe +E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.CodeVisualizer\obj\Debug\MetaCode.CodeVisualizer.pdb diff --git a/project/MetaCode/MetaCode.CodeVisualizer/obj/Debug/MetaCode.CodeVisualizer.csproj.GenerateResource.Cache b/project/MetaCode/MetaCode.CodeVisualizer/obj/Debug/MetaCode.CodeVisualizer.csproj.GenerateResource.Cache new file mode 100644 index 0000000..932e47a Binary files /dev/null and b/project/MetaCode/MetaCode.CodeVisualizer/obj/Debug/MetaCode.CodeVisualizer.csproj.GenerateResource.Cache differ diff --git a/project/MetaCode/MetaCode.CodeVisualizer/obj/Debug/MetaCode.CodeVisualizer.csprojResolveAssemblyReference.cache b/project/MetaCode/MetaCode.CodeVisualizer/obj/Debug/MetaCode.CodeVisualizer.csprojResolveAssemblyReference.cache new file mode 100644 index 0000000..4117ac5 Binary files /dev/null and b/project/MetaCode/MetaCode.CodeVisualizer/obj/Debug/MetaCode.CodeVisualizer.csprojResolveAssemblyReference.cache differ diff --git a/project/MetaCode/MetaCode.CodeVisualizer/obj/Debug/MetaCode.CodeVisualizer.exe b/project/MetaCode/MetaCode.CodeVisualizer/obj/Debug/MetaCode.CodeVisualizer.exe new file mode 100644 index 0000000..3c44ee8 Binary files /dev/null and b/project/MetaCode/MetaCode.CodeVisualizer/obj/Debug/MetaCode.CodeVisualizer.exe differ diff --git a/project/MetaCode/MetaCode.CodeVisualizer/obj/Debug/MetaCode.CodeVisualizer.g.resources b/project/MetaCode/MetaCode.CodeVisualizer/obj/Debug/MetaCode.CodeVisualizer.g.resources new file mode 100644 index 0000000..76dfd95 Binary files /dev/null and b/project/MetaCode/MetaCode.CodeVisualizer/obj/Debug/MetaCode.CodeVisualizer.g.resources differ diff --git a/project/MetaCode/MetaCode.CodeVisualizer/obj/Debug/MetaCode.CodeVisualizer.pdb b/project/MetaCode/MetaCode.CodeVisualizer/obj/Debug/MetaCode.CodeVisualizer.pdb new file mode 100644 index 0000000..064f634 Binary files /dev/null and b/project/MetaCode/MetaCode.CodeVisualizer/obj/Debug/MetaCode.CodeVisualizer.pdb differ diff --git a/project/MetaCode/MetaCode.CodeVisualizer/obj/Debug/MetaCode.CodeVisualizer_MarkupCompile.cache b/project/MetaCode/MetaCode.CodeVisualizer/obj/Debug/MetaCode.CodeVisualizer_MarkupCompile.cache new file mode 100644 index 0000000..1d115ea --- /dev/null +++ b/project/MetaCode/MetaCode.CodeVisualizer/obj/Debug/MetaCode.CodeVisualizer_MarkupCompile.cache @@ -0,0 +1,20 @@ +MetaCode.CodeVisualizer + + +winexe +C# +.cs +E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.CodeVisualizer\obj\Debug\ +MetaCode.CodeVisualizer +none +false +DEBUG;TRACE +E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.CodeVisualizer\App.xaml +11151548125 + +5-2017746502 +12-1401562060 +MainWindow.xaml; + +False + diff --git a/project/MetaCode/MetaCode.CodeVisualizer/obj/Debug/MetaCode.CodeVisualizer_MarkupCompile.i.cache b/project/MetaCode/MetaCode.CodeVisualizer/obj/Debug/MetaCode.CodeVisualizer_MarkupCompile.i.cache new file mode 100644 index 0000000..e8a96c6 --- /dev/null +++ b/project/MetaCode/MetaCode.CodeVisualizer/obj/Debug/MetaCode.CodeVisualizer_MarkupCompile.i.cache @@ -0,0 +1,20 @@ +MetaCode.CodeVisualizer + + +winexe +C# +.cs +E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.CodeVisualizer\obj\Debug\ +MetaCode.CodeVisualizer +none +false +DEBUG;TRACE +E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.CodeVisualizer\App.xaml +11151548125 + +91378088775 +12-1401562060 +MainWindow.xaml; + +False + diff --git a/project/MetaCode/MetaCode.CodeVisualizer/obj/Debug/TemporaryGeneratedFile_036C0B5B-1481-4323-8D20-8F5ADCB23D92.cs b/project/MetaCode/MetaCode.CodeVisualizer/obj/Debug/TemporaryGeneratedFile_036C0B5B-1481-4323-8D20-8F5ADCB23D92.cs new file mode 100644 index 0000000..e69de29 diff --git a/project/MetaCode/MetaCode.CodeVisualizer/obj/Debug/TemporaryGeneratedFile_5937a670-0e60-4077-877b-f7221da3dda1.cs b/project/MetaCode/MetaCode.CodeVisualizer/obj/Debug/TemporaryGeneratedFile_5937a670-0e60-4077-877b-f7221da3dda1.cs new file mode 100644 index 0000000..e69de29 diff --git a/project/MetaCode/MetaCode.CodeVisualizer/obj/Debug/TemporaryGeneratedFile_E7A71F73-0F8D-4B9B-B56E-8E70B10BC5D3.cs b/project/MetaCode/MetaCode.CodeVisualizer/obj/Debug/TemporaryGeneratedFile_E7A71F73-0F8D-4B9B-B56E-8E70B10BC5D3.cs new file mode 100644 index 0000000..e69de29 diff --git a/project/MetaCode/MetaCode.Compiler.Tests/AbstractTreeVisitorTestFixture.cs b/project/MetaCode/MetaCode.Compiler.Tests/AbstractTreeVisitorTestFixture.cs index 2ecbecf..f5491d3 100644 --- a/project/MetaCode/MetaCode.Compiler.Tests/AbstractTreeVisitorTestFixture.cs +++ b/project/MetaCode/MetaCode.Compiler.Tests/AbstractTreeVisitorTestFixture.cs @@ -75,8 +75,33 @@ public void AbstractTreeVisitorDeclareVariableTest() { var source = MultiLine( "var a : System.Boolean = false;", - "do", - " var b = a;", + "if (a) ", + " var b = 10;", + "else if (false) ", + " skip;", + "else if (true) ", + " skip;", + "else if (false) ", + " skip;", + "else", + " var b = 10;", + "end;", + "foreach (var ch in \"Hello World\") do", + " var b = ch;", + "end;" + ); + + var result = ParseWithAbstractTreeVisitor(Compiler, source); + } + + [Test] + public void AbstractTreeVisitorDeclareFunctionTest() + { + var source = MultiLine( + "var max = function(a: System.Int32, b: System.Int32): System.Int32 do", + " if (true)", + " var c = 10;", + " end;", "end;" ); diff --git a/project/MetaCode/MetaCode.Compiler.Tests/CustomVisitorTestFixture.cs b/project/MetaCode/MetaCode.Compiler.Tests/CustomVisitorTestFixture.cs new file mode 100644 index 0000000..684e79a --- /dev/null +++ b/project/MetaCode/MetaCode.Compiler.Tests/CustomVisitorTestFixture.cs @@ -0,0 +1,145 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using System.Linq.Expressions; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using MetaCode.Compiler.AbstractTree; +using MetaCode.Compiler.AbstractTree.Constants; +using MetaCode.Compiler.AbstractTree.Expressions; +using MetaCode.Compiler.AbstractTree.Operators; +using MetaCode.Compiler.AbstractTree.Operators.Numerics; +using MetaCode.Compiler.AbstractTree.Statements; +using MetaCode.Compiler.AbstractTree.Visitors; +using MetaCode.Compiler.Services; +using MetaCode.Compiler.Visitors; +using NUnit.Framework; + +namespace MetaCode.Compiler.Tests +{ + [TestFixture] + public class CustomVisitorTestFixture : CompilerTestFixtureBase + { + #region Helper methods + + private Node ParseWithAbstractTreeVisitor(MetaCodeCompiler compiler, string source) + { + return compiler.ParseWithVisitor(source, () => new AbstractTreeVisitor(CompilerService.Instance)); + } + + #endregion + + public MetaCodeCompiler Compiler { get; set; } + + [SetUp] + public void SetUp() + { + Compiler = new MetaCodeCompiler(); + } + + [Test] + public void CustomVisitorTest() + { + var source = MultiLine( + "var a = function(c: System.Int32): System.Void do" + + " var b = 45; " + + " if (b == 10)" + + " skip;" + + " end;" + + "end;" + ); + + var result = ParseWithAbstractTreeVisitor(Compiler, source); + var text = new StringBuilder(); + var visitor = new TreeVisitorBase(); + visitor + .Clear() + .If((_visitor, node) => + { + _visitor.VisitChild(node.Left); + text.Append(" " + node.Operator.Operator + " "); + _visitor.VisitChild(node.Right); + var op = node.Operator.Operator; + return null; + }) + .If((_visitor, node) => _visitor.VisitChild(node.InitialValue)) + .If((_visitor, node) => text.Append(string.Join(Environment.NewLine, node.Statements.Select(_visitor.VisitChild)))) + .If((_visitor, node) => text.Append(node.Value.ToString(CultureInfo.InvariantCulture))) + .VisitChild(result); + + text.Clear().AppendLine("digraph {"); + var nodes = new List>(); + visitor.Clear() + .DefaultVisitor((_visitor, node) => + { + var name = "node" + nodes.Count; + var type = node.GetType().Name.First().ToString(); + nodes.Add(Tuple.Create(name, type)); + + if (!node.Children.Any()) + text.Append(name + ";"); + + foreach (var child in node.Children) + { + text.Append(name + "->"); + _visitor.VisitChild(child); + } + + return null; + }) + .VisitChild(result); + text.AppendLine(); + foreach (var tuple in nodes) + text.AppendLine(string.Format("{0} [label=\"{1}\"];", tuple.Item1, tuple.Item2)); + var dot = text.AppendLine("}").ToString(); + } + + [Test] + public void CustomVisitorGraphConverterTest() + { + var source = MultiLine( + "var a = function(c: System.Int32): System.Void do" + + " var b = 45; " + + " if (b == 10)" + + " skip;" + + " end;" + + "end;" + ); + + var result = ParseWithAbstractTreeVisitor(Compiler, source); + var text = new StringBuilder(); + var visitor = new TreeVisitorBase(); + var nodes = new List>(); + var edges = new List>(); + visitor + .Clear() + .DefaultVisitor((_visitor, node) => + { + var name = "node" + nodes.Count; + var type = node.GetType().Name; + nodes.Add(Tuple.Create(name, type)); + + foreach (var child in node.Children) + { + var childName = _visitor.VisitChild(child); + edges.Add(Tuple.Create(name, childName)); + } + + return name; + }) + .VisitChild(result); + + foreach (var node in nodes) + text.AppendLine(string.Format("g.addNode(\"{0}\", {{ label: \"{1}\" }});", node.Item1, node.Item2)); + + foreach (var edge in edges) + text.AppendLine(string.Format("g.addEdge(\"{0}\", \"{1}\");", edge.Item1, edge.Item2)); + + var js = text.ToString(); + + } + } +} diff --git a/project/MetaCode/MetaCode.Compiler.Tests/MetaCode.Compiler.Tests.csproj b/project/MetaCode/MetaCode.Compiler.Tests/MetaCode.Compiler.Tests.csproj index 665eb81..0ba5b9c 100644 --- a/project/MetaCode/MetaCode.Compiler.Tests/MetaCode.Compiler.Tests.csproj +++ b/project/MetaCode/MetaCode.Compiler.Tests/MetaCode.Compiler.Tests.csproj @@ -47,6 +47,7 @@ + diff --git a/project/MetaCode/MetaCode.Compiler.Tests/bin/Debug/MetaCode.Compiler.Tests.dll b/project/MetaCode/MetaCode.Compiler.Tests/bin/Debug/MetaCode.Compiler.Tests.dll index 35bdfc5..84b2cf7 100644 Binary files a/project/MetaCode/MetaCode.Compiler.Tests/bin/Debug/MetaCode.Compiler.Tests.dll and b/project/MetaCode/MetaCode.Compiler.Tests/bin/Debug/MetaCode.Compiler.Tests.dll differ diff --git a/project/MetaCode/MetaCode.Compiler.Tests/bin/Debug/MetaCode.Compiler.Tests.pdb b/project/MetaCode/MetaCode.Compiler.Tests/bin/Debug/MetaCode.Compiler.Tests.pdb index 85e9952..e0ace11 100644 Binary files a/project/MetaCode/MetaCode.Compiler.Tests/bin/Debug/MetaCode.Compiler.Tests.pdb and b/project/MetaCode/MetaCode.Compiler.Tests/bin/Debug/MetaCode.Compiler.Tests.pdb differ diff --git a/project/MetaCode/MetaCode.Compiler.Tests/bin/Debug/MetaCode.Compiler.dll b/project/MetaCode/MetaCode.Compiler.Tests/bin/Debug/MetaCode.Compiler.dll index 12d8c29..bd9598f 100644 Binary files a/project/MetaCode/MetaCode.Compiler.Tests/bin/Debug/MetaCode.Compiler.dll and b/project/MetaCode/MetaCode.Compiler.Tests/bin/Debug/MetaCode.Compiler.dll differ diff --git a/project/MetaCode/MetaCode.Compiler.Tests/bin/Debug/MetaCode.Compiler.pdb b/project/MetaCode/MetaCode.Compiler.Tests/bin/Debug/MetaCode.Compiler.pdb index 01889c7..a7ead44 100644 Binary files a/project/MetaCode/MetaCode.Compiler.Tests/bin/Debug/MetaCode.Compiler.pdb and b/project/MetaCode/MetaCode.Compiler.Tests/bin/Debug/MetaCode.Compiler.pdb differ diff --git a/project/MetaCode/MetaCode.Compiler.Tests/bin/Debug/MetaCode.Core.dll b/project/MetaCode/MetaCode.Compiler.Tests/bin/Debug/MetaCode.Core.dll index 1e0094a..97dba1b 100644 Binary files a/project/MetaCode/MetaCode.Compiler.Tests/bin/Debug/MetaCode.Core.dll and b/project/MetaCode/MetaCode.Compiler.Tests/bin/Debug/MetaCode.Core.dll differ diff --git a/project/MetaCode/MetaCode.Compiler.Tests/bin/Debug/MetaCode.Core.pdb b/project/MetaCode/MetaCode.Compiler.Tests/bin/Debug/MetaCode.Core.pdb index 5f99b8d..ebdaef2 100644 Binary files a/project/MetaCode/MetaCode.Compiler.Tests/bin/Debug/MetaCode.Core.pdb and b/project/MetaCode/MetaCode.Compiler.Tests/bin/Debug/MetaCode.Core.pdb differ diff --git a/project/MetaCode/MetaCode.Compiler.Tests/obj/Debug/DesignTimeResolveAssemblyReferencesInput.cache b/project/MetaCode/MetaCode.Compiler.Tests/obj/Debug/DesignTimeResolveAssemblyReferencesInput.cache index 2ad48ed..9bd3ec2 100644 Binary files a/project/MetaCode/MetaCode.Compiler.Tests/obj/Debug/DesignTimeResolveAssemblyReferencesInput.cache and b/project/MetaCode/MetaCode.Compiler.Tests/obj/Debug/DesignTimeResolveAssemblyReferencesInput.cache differ diff --git a/project/MetaCode/MetaCode.Compiler.Tests/obj/Debug/MetaCode.Compiler.Tests.csproj.FileListAbsolute.txt b/project/MetaCode/MetaCode.Compiler.Tests/obj/Debug/MetaCode.Compiler.Tests.csproj.FileListAbsolute.txt index 002c7a1..b24bbec 100644 --- a/project/MetaCode/MetaCode.Compiler.Tests/obj/Debug/MetaCode.Compiler.Tests.csproj.FileListAbsolute.txt +++ b/project/MetaCode/MetaCode.Compiler.Tests/obj/Debug/MetaCode.Compiler.Tests.csproj.FileListAbsolute.txt @@ -1,16 +1,3 @@ -E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler.Tests\bin\Debug\MetaCode.Compiler.Tests.dll -E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler.Tests\bin\Debug\MetaCode.Compiler.Tests.pdb -E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler.Tests\bin\Debug\MetaCode.Compiler.dll -E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler.Tests\bin\Debug\nunit.framework.dll -E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler.Tests\bin\Debug\Antlr4.Runtime.v4.5.dll -E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler.Tests\bin\Debug\MetaCode.Compiler.pdb -E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler.Tests\bin\Debug\nunit.framework.xml -E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler.Tests\bin\Debug\Antlr4.Runtime.v4.5.xml -E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler.Tests\obj\Debug\MetaCode.Compiler.Tests.csprojResolveAssemblyReference.cache -E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler.Tests\obj\Debug\MetaCode.Compiler.Tests.dll -E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler.Tests\obj\Debug\MetaCode.Compiler.Tests.pdb -E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler.Tests\bin\Debug\MetaCode.Core.dll -E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler.Tests\bin\Debug\MetaCode.Core.pdb E:\Development\Projects\C#\MetaCode\project\MetaCode\MetaCode.Compiler.Tests\bin\Debug\MetaCode.Compiler.Tests.dll E:\Development\Projects\C#\MetaCode\project\MetaCode\MetaCode.Compiler.Tests\bin\Debug\MetaCode.Compiler.Tests.pdb E:\Development\Projects\C#\MetaCode\project\MetaCode\MetaCode.Compiler.Tests\bin\Debug\MetaCode.Compiler.dll @@ -24,3 +11,16 @@ E:\Development\Projects\C#\MetaCode\project\MetaCode\MetaCode.Compiler.Tests\bin E:\Development\Projects\C#\MetaCode\project\MetaCode\MetaCode.Compiler.Tests\obj\Debug\MetaCode.Compiler.Tests.csprojResolveAssemblyReference.cache E:\Development\Projects\C#\MetaCode\project\MetaCode\MetaCode.Compiler.Tests\obj\Debug\MetaCode.Compiler.Tests.dll E:\Development\Projects\C#\MetaCode\project\MetaCode\MetaCode.Compiler.Tests\obj\Debug\MetaCode.Compiler.Tests.pdb +E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler.Tests\bin\Debug\MetaCode.Compiler.Tests.dll +E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler.Tests\bin\Debug\MetaCode.Compiler.Tests.pdb +E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler.Tests\bin\Debug\Antlr4.Runtime.v4.5.dll +E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler.Tests\bin\Debug\MetaCode.Compiler.dll +E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler.Tests\bin\Debug\nunit.framework.dll +E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler.Tests\bin\Debug\MetaCode.Core.dll +E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler.Tests\bin\Debug\MetaCode.Compiler.pdb +E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler.Tests\bin\Debug\Antlr4.Runtime.v4.5.xml +E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler.Tests\bin\Debug\nunit.framework.xml +E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler.Tests\bin\Debug\MetaCode.Core.pdb +E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler.Tests\obj\Debug\MetaCode.Compiler.Tests.csprojResolveAssemblyReference.cache +E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler.Tests\obj\Debug\MetaCode.Compiler.Tests.dll +E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler.Tests\obj\Debug\MetaCode.Compiler.Tests.pdb diff --git a/project/MetaCode/MetaCode.Compiler.Tests/obj/Debug/MetaCode.Compiler.Tests.csprojResolveAssemblyReference.cache b/project/MetaCode/MetaCode.Compiler.Tests/obj/Debug/MetaCode.Compiler.Tests.csprojResolveAssemblyReference.cache index 99a5955..16525f0 100644 Binary files a/project/MetaCode/MetaCode.Compiler.Tests/obj/Debug/MetaCode.Compiler.Tests.csprojResolveAssemblyReference.cache and b/project/MetaCode/MetaCode.Compiler.Tests/obj/Debug/MetaCode.Compiler.Tests.csprojResolveAssemblyReference.cache differ diff --git a/project/MetaCode/MetaCode.Compiler.Tests/obj/Debug/MetaCode.Compiler.Tests.dll b/project/MetaCode/MetaCode.Compiler.Tests/obj/Debug/MetaCode.Compiler.Tests.dll index 35bdfc5..84b2cf7 100644 Binary files a/project/MetaCode/MetaCode.Compiler.Tests/obj/Debug/MetaCode.Compiler.Tests.dll and b/project/MetaCode/MetaCode.Compiler.Tests/obj/Debug/MetaCode.Compiler.Tests.dll differ diff --git a/project/MetaCode/MetaCode.Compiler.Tests/obj/Debug/MetaCode.Compiler.Tests.pdb b/project/MetaCode/MetaCode.Compiler.Tests/obj/Debug/MetaCode.Compiler.Tests.pdb index 85e9952..e0ace11 100644 Binary files a/project/MetaCode/MetaCode.Compiler.Tests/obj/Debug/MetaCode.Compiler.Tests.pdb and b/project/MetaCode/MetaCode.Compiler.Tests/obj/Debug/MetaCode.Compiler.Tests.pdb differ diff --git a/project/MetaCode/MetaCode.Compiler.Tests/obj/Release/MetaCode.Compiler.Tests.csprojResolveAssemblyReference.cache b/project/MetaCode/MetaCode.Compiler.Tests/obj/Release/MetaCode.Compiler.Tests.csprojResolveAssemblyReference.cache new file mode 100644 index 0000000..cca9b58 Binary files /dev/null and b/project/MetaCode/MetaCode.Compiler.Tests/obj/Release/MetaCode.Compiler.Tests.csprojResolveAssemblyReference.cache differ diff --git a/project/MetaCode/MetaCode.Compiler/AbstractTree/Expressions/AssignmentExpressionNode.cs b/project/MetaCode/MetaCode.Compiler/AbstractTree/Expressions/AssignmentExpressionNode.cs index 7e49b10..530a6d6 100644 --- a/project/MetaCode/MetaCode.Compiler/AbstractTree/Expressions/AssignmentExpressionNode.cs +++ b/project/MetaCode/MetaCode.Compiler/AbstractTree/Expressions/AssignmentExpressionNode.cs @@ -3,26 +3,29 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using MetaCode.Compiler.Commons; +using MetaCode.Compiler.Grammar; using MetaCode.Core; namespace MetaCode.Compiler.AbstractTree.Expressions { public class AssignmentExpressionNode : PrimaryExpressionNode { - public Identifier Identifier { get; internal set; } + public VariableDeclaration Variable { get; internal set; } + public ExpressionNode Value { get; internal set; } - public AssignmentExpressionNode(Identifier identifier, ExpressionNode value, IEnumerable attributes) : base(attributes) + public AssignmentExpressionNode(VariableDeclaration variable, ExpressionNode value, IEnumerable attributes) : base(attributes) { - if (identifier == null) - ThrowHelper.ThrowArgumentNullException(() => identifier); + if (variable == null) + ThrowHelper.ThrowArgumentNullException(() => variable); if (value == null) ThrowHelper.ThrowArgumentNullException(() => value); - Identifier = identifier; + Variable = variable; Value = value; - AddChildren(Identifier, Value); + AddChildren(Value); } public override Type Type diff --git a/project/MetaCode/MetaCode.Compiler/AbstractTree/Expressions/FunctionExpressionNode.cs b/project/MetaCode/MetaCode.Compiler/AbstractTree/Expressions/FunctionExpressionNode.cs index e0fd04b..184c02e 100644 --- a/project/MetaCode/MetaCode.Compiler/AbstractTree/Expressions/FunctionExpressionNode.cs +++ b/project/MetaCode/MetaCode.Compiler/AbstractTree/Expressions/FunctionExpressionNode.cs @@ -10,25 +10,27 @@ namespace MetaCode.Compiler.AbstractTree.Expressions { public class FunctionExpressionNode : PrimaryExpressionNode { - public Identifier Name { get; protected set; } + public string Name { get; protected set; } + public BlockStatementNode FunctionBlock { get; protected set; } + public TypeNameNode ReturnType { get; protected set; } - public FunctionExpressionNode(Identifier name, BlockStatementNode functionBlock, TypeNameNode returnType, IEnumerable attributes) + public bool IsAnonymFunction { get { return string.IsNullOrWhiteSpace(Name); } } + + public FunctionExpressionNode(string name, BlockStatementNode functionBlock, TypeNameNode returnType, IEnumerable attributes) : base(attributes) { - if (name == null) - ThrowHelper.ThrowArgumentNullException(() => name); if (functionBlock == null) ThrowHelper.ThrowArgumentNullException(() => functionBlock); if (returnType == null) ThrowHelper.ThrowArgumentNullException(() => returnType); - + Name = name; FunctionBlock = functionBlock; ReturnType = returnType; - AddChildren(Name, FunctionBlock, ReturnType); + AddChildren(FunctionBlock, ReturnType); } public override Type Type diff --git a/project/MetaCode/MetaCode.Compiler/AbstractTree/Factories/ConstantLiteralFactory.cs b/project/MetaCode/MetaCode.Compiler/AbstractTree/Factories/ConstantLiteralFactory.cs index f80e6f6..f8ddf31 100644 --- a/project/MetaCode/MetaCode.Compiler/AbstractTree/Factories/ConstantLiteralFactory.cs +++ b/project/MetaCode/MetaCode.Compiler/AbstractTree/Factories/ConstantLiteralFactory.cs @@ -23,6 +23,7 @@ public ConstantLiteralFactory(CompilerService compilerService) ThrowHelper.ThrowArgumentNullException(() => compilerService); CompilerService = compilerService; + CompilerService.ConstantLiteralFactory = this; } public NumberConstantLiteralNode Number(double value) diff --git a/project/MetaCode/MetaCode.Compiler/AbstractTree/Factories/ExpressionFactory.cs b/project/MetaCode/MetaCode.Compiler/AbstractTree/Factories/ExpressionFactory.cs index 0da49e0..8bf433d 100644 --- a/project/MetaCode/MetaCode.Compiler/AbstractTree/Factories/ExpressionFactory.cs +++ b/project/MetaCode/MetaCode.Compiler/AbstractTree/Factories/ExpressionFactory.cs @@ -1,11 +1,13 @@ using System; using System.Collections.Generic; +using System.Linq; using MetaCode.Compiler.AbstractTree.Constants; using MetaCode.Compiler.AbstractTree.Expressions; using MetaCode.Compiler.AbstractTree.Operators; using MetaCode.Compiler.AbstractTree.Operators.Logical; using MetaCode.Compiler.AbstractTree.Operators.Numerics; using MetaCode.Compiler.AbstractTree.Operators.Relational; +using MetaCode.Compiler.AbstractTree.Statements; using MetaCode.Compiler.Helpers; using MetaCode.Compiler.Services; using MetaCode.Core; @@ -40,6 +42,22 @@ public ExpressionFactory(CompilerService compilerService) }; CompilerService = compilerService; + CompilerService.ExpressionFactory = this; + } + + public AssignmentExpressionNode Assignment(string identifier, ExpressionNode expression, IEnumerable attributes) + { + if (string.IsNullOrWhiteSpace(identifier)) + ThrowHelper.ThrowException("The identifier is blank!"); + + if (expression == null) + ThrowHelper.ThrowArgumentNullException(() => expression); + + var variable = CompilerService.FindVariable(identifier); + if (variable == null) + CompilerService.Error(string.Format("Undefined variable: '{0}'", identifier)); + + return new AssignmentExpressionNode(variable, expression, attributes); } public ConstantExpressionNode Constant(ConstantLiteralNode constant) @@ -62,7 +80,8 @@ public BinaryExpressionNode BinaryOperand(ExpressionNode left, ExpressionNode ri if (!_operators.TryGetValue(@operator, out operatorNode)) throw new Exception("Unsupported operator!"); - if (operatorNode is LogicalBinaryOperatorNode) { + if (operatorNode is LogicalBinaryOperatorNode) + { if (!left.Type.IsLogical()) CompilerService.Error("Left expression must be a boolean expression!"); if (!right.Type.IsLogical()) @@ -71,7 +90,8 @@ public BinaryExpressionNode BinaryOperand(ExpressionNode left, ExpressionNode ri return new BinaryExpressionNode(left, right, operatorNode as LogicalBinaryOperatorNode); } - if (operatorNode is NumericBinaryOperatorNode) { + if (operatorNode is NumericBinaryOperatorNode) + { if (!left.Type.IsNumeric()) CompilerService.Error("Left expression must be a numeric expression!"); if (!right.Type.IsNumeric()) @@ -80,7 +100,8 @@ public BinaryExpressionNode BinaryOperand(ExpressionNode left, ExpressionNode ri return new BinaryExpressionNode(left, right, operatorNode as NumericBinaryOperatorNode); } - if (operatorNode is RelationalBinaryOperatorNode) { + if (operatorNode is RelationalBinaryOperatorNode) + { if (!left.Type.IsNumeric()) CompilerService.Error("Left expression must be a numeric expression!"); if (!right.Type.IsNumeric()) @@ -120,5 +141,60 @@ public TypeNameNode Type(string[] identifiers) return new TypeNameNode(type); } + + public FunctionExpressionNode Function(string name, Type returnType, FunctionParameterNode[] parameters, Lazy body) + { + var statementFactory = CompilerService.StatementFactory; + + return Function(name, returnType, parameters, + new Lazy(() => + statementFactory.Block(new Lazy(() => new StatementNodeBase[] { statementFactory.Expression(body.Value) })))); + } + + public FunctionExpressionNode Function(string name, Type returnType, FunctionParameterNode[] parameters, Lazy body) + { + if (returnType == null) + ThrowHelper.ThrowArgumentNullException(() => returnType); + if (body == null) + ThrowHelper.ThrowArgumentNullException(() => body); + if (parameters == null) + ThrowHelper.ThrowArgumentNullException(() => parameters); + + var scope = CompilerService.GetScope(); + + if (!string.IsNullOrWhiteSpace(name)) + { + if (CompilerService.FindFunction(name) != null) + CompilerService.Error(string.Format("The '{0}' is already defined!", name)); + + scope.DeclareFunction(name, returnType, parameters.Select(param => param.TypeName.Type).ToArray()); + } + var functionScope = CompilerService.PushScope(); + + foreach (var parameter in parameters) + functionScope.DeclareVariable(parameter.Name, parameter.TypeName.Type); + + var bodyStatement = body.Value; + CompilerService.PopScope(); + + return new FunctionExpressionNode(name, bodyStatement, new TypeNameNode(returnType), null); + } + + public FunctionParameterNode FunctionFormalParameter(string name, TypeNameNode type, IEnumerable attributes) + { + if (string.IsNullOrWhiteSpace(name)) + ThrowHelper.ThrowException("The name is blank!"); + if (type == null) + ThrowHelper.ThrowArgumentNullException(() => type); + + return new FunctionParameterNode(name, type, attributes); + } + + public MemberExpressionNode Member(IEnumerable members) + { + if (members == null) + ThrowHelper.ThrowArgumentNullException(() => members); + return new MemberExpressionNode(members.ToArray()); + } } } \ No newline at end of file diff --git a/project/MetaCode/MetaCode.Compiler/AbstractTree/Factories/StatementFactory.cs b/project/MetaCode/MetaCode.Compiler/AbstractTree/Factories/StatementFactory.cs index 0d42982..6223be3 100644 --- a/project/MetaCode/MetaCode.Compiler/AbstractTree/Factories/StatementFactory.cs +++ b/project/MetaCode/MetaCode.Compiler/AbstractTree/Factories/StatementFactory.cs @@ -4,6 +4,7 @@ using MetaCode.Compiler.AbstractTree.Constants; using MetaCode.Compiler.AbstractTree.Expressions; using MetaCode.Compiler.AbstractTree.Statements; +using MetaCode.Compiler.Commons; using MetaCode.Compiler.Grammar; using MetaCode.Compiler.Helpers; using MetaCode.Compiler.Services; @@ -11,6 +12,7 @@ namespace MetaCode.Compiler.AbstractTree.Factories { + // TODO: Implement return statement! public class StatementFactory { public CompilerService CompilerService { get; protected set; } @@ -21,9 +23,15 @@ public StatementFactory(CompilerService compilerService) ThrowHelper.ThrowArgumentNullException(() => compilerService); CompilerService = compilerService; + CompilerService.StatementFactory = this; } - public IfStatementNode IfThenElse(ExpressionNode condition, StatementNodeBase trueStatement, StatementNodeBase falseStatement = null) + public IfStatementNode IfThenElse(ExpressionNode condition, BlockStatementNode trueStatement) + { + return IfThenElse(condition, trueStatement, new IfStatementNode[0], null); + } + + public IfStatementNode IfThenElse(ExpressionNode condition, BlockStatementNode trueStatement, IfStatementNode[] elseIfStatements, BlockStatementNode falseStatement = null) { if (condition == null) ThrowHelper.ThrowArgumentNullException(() => condition); @@ -35,6 +43,20 @@ public IfStatementNode IfThenElse(ExpressionNode condition, StatementNodeBase tr if (condition.Type != typeof(bool)) CompilerService.Error("Condition must be logical expression!"); + if (elseIfStatements.Any()) + { + var elseIf = elseIfStatements.First(); + falseStatement = + Block( + new Lazy( + () => new StatementNodeBase[] + { + IfThenElse(elseIf.ConditionExpression, elseIf.TrueStatementNode, elseIfStatements.Skip(1).ToArray()) + } + ) + ); + } + return new IfStatementNode(condition, trueStatement, falseStatement); } @@ -43,10 +65,10 @@ public SkipStatementNode Skip() return new SkipStatementNode(); } - public ForeachLoopStatementNode Foreach(Variable variable, ExpressionNode expression, StatementNodeBase body, bool createLoopVariable) + public ForeachLoopStatementNode Foreach(string variable, TypeNameNode variableType, ExpressionNode expression, Lazy body, bool createLoopVariable) { - if (variable == null) - ThrowHelper.ThrowArgumentNullException(() => variable); + if (string.IsNullOrWhiteSpace(variable)) + ThrowHelper.ThrowException("The variable is blank!"); if (expression == null) ThrowHelper.ThrowArgumentNullException(() => expression); if (body == null) @@ -55,7 +77,20 @@ public ForeachLoopStatementNode Foreach(Variable variable, ExpressionNode expres if (!expression.Type.IsEnumerable()) CompilerService.Error("The expression of foreach must be enumerable!"); - return new ForeachLoopStatementNode(variable, expression, body); + VariableDeclaration variableNode = null; + + if (createLoopVariable) + { + var scope = CompilerService.PushScope(); + variableNode = scope.DeclareVariable(variable, variableType.With(type => type.Type) ?? expression.Type.GetItemType()); + var statements = body.Value; + CompilerService.PopScope(); + + return new ForeachLoopStatementNode(variableNode, expression, statements); + } + + variableNode = CompilerService.FindVariable(variable); + return new ForeachLoopStatementNode(variableNode, expression, body.Value); } public WhileLoopStatementNode While(ExpressionNode condition, StatementNodeBase body) @@ -87,7 +122,8 @@ public VariableDeclarationStatementNode DeclareVariable(string name, TypeNameNod Type type = null; - if (typeName == null) { + if (typeName == null) + { if (initialValue == null) CompilerService.Error("Invalid variable declaration! You must define the type of variable!"); else @@ -99,7 +135,8 @@ public VariableDeclarationStatementNode DeclareVariable(string name, TypeNameNod var scope = CompilerService.GetScope(); var declaration = scope.DeclareVariable(name, type); - if (initialValue == null) { + if (initialValue == null) + { ConstantLiteralNode constant = null; if (type.IsLogical()) constant = new BooleanConstantLiteralNode(false); @@ -116,17 +153,17 @@ public VariableDeclarationStatementNode DeclareVariable(string name, TypeNameNod public BlockStatementNode EmptyBlock() { - return Block(() => new StatementNodeBase[0]); + return Block(new Lazy(() => new StatementNodeBase[0])); } - public BlockStatementNode Block(Func statementsFunction) + public BlockStatementNode Block(Lazy statementsFunction) { if (statementsFunction == null) ThrowHelper.ThrowArgumentNullException(() => statementsFunction); var scope = CompilerService.PushScope(); - var statements = statementsFunction(); + var statements = statementsFunction.Value; if (statements.Any(statement => statement == null)) ThrowHelper.ThrowException("There is a statement which is null!"); diff --git a/project/MetaCode/MetaCode.Compiler/AbstractTree/FunctionParameter.cs b/project/MetaCode/MetaCode.Compiler/AbstractTree/FunctionParameterNode.cs similarity index 80% rename from project/MetaCode/MetaCode.Compiler/AbstractTree/FunctionParameter.cs rename to project/MetaCode/MetaCode.Compiler/AbstractTree/FunctionParameterNode.cs index 60ae0c1..42b52e6 100644 --- a/project/MetaCode/MetaCode.Compiler/AbstractTree/FunctionParameter.cs +++ b/project/MetaCode/MetaCode.Compiler/AbstractTree/FunctionParameterNode.cs @@ -6,15 +6,15 @@ namespace MetaCode.Compiler.AbstractTree { - public class FunctionParameter : Node + public class FunctionParameterNode : Node { public IEnumerable Attributes { get; internal set; } - public Identifier Name { get; internal set; } + public string Name { get; internal set; } public TypeNameNode TypeName { get; internal set; } - public FunctionParameter(Identifier name, TypeNameNode typeName, IEnumerable attributes) + public FunctionParameterNode(string name, TypeNameNode typeName, IEnumerable attributes) { if (name == null) throw new ArgumentNullException("name", "The name is "); if (typeName == null) throw new ArgumentNullException("typeName"); diff --git a/project/MetaCode/MetaCode.Compiler/AbstractTree/Node.cs b/project/MetaCode/MetaCode.Compiler/AbstractTree/Node.cs index d5f1a7c..2916bdb 100644 --- a/project/MetaCode/MetaCode.Compiler/AbstractTree/Node.cs +++ b/project/MetaCode/MetaCode.Compiler/AbstractTree/Node.cs @@ -9,7 +9,7 @@ public abstract class Node { #region Private fields - protected readonly List _children; + protected List _children; #endregion @@ -29,7 +29,7 @@ internal Node AddChildren(params Node[] children) if (children == null) throw new ArgumentNullException("children", "The children is null!"); - _children.Union(children); + _children = _children.Union(children).ToList(); foreach (var child in children) child.SetParent(this); diff --git a/project/MetaCode/MetaCode.Compiler/AbstractTree/Statements/BlockStatementNode.cs b/project/MetaCode/MetaCode.Compiler/AbstractTree/Statements/BlockStatementNode.cs index df6d35f..e661df6 100644 --- a/project/MetaCode/MetaCode.Compiler/AbstractTree/Statements/BlockStatementNode.cs +++ b/project/MetaCode/MetaCode.Compiler/AbstractTree/Statements/BlockStatementNode.cs @@ -10,8 +10,6 @@ namespace MetaCode.Compiler.AbstractTree.Statements { public class BlockStatementNode : StatementNodeBase { - public IEnumerable Variables { get; protected set; } - public IEnumerable Statements { get; protected set; } public Scope Scope { get; protected set; } @@ -34,7 +32,7 @@ public BlockStatementNode(IEnumerable statements, Scope scope public override IEnumerable Children { - get { return base.Children.Concat(Statements).Concat(Variables); } + get { return base.Children.Concat(Statements); } } } } diff --git a/project/MetaCode/MetaCode.Compiler/AbstractTree/Statements/ForeachLoopStatementNode.cs b/project/MetaCode/MetaCode.Compiler/AbstractTree/Statements/ForeachLoopStatementNode.cs index 45d92ea..e76493b 100644 --- a/project/MetaCode/MetaCode.Compiler/AbstractTree/Statements/ForeachLoopStatementNode.cs +++ b/project/MetaCode/MetaCode.Compiler/AbstractTree/Statements/ForeachLoopStatementNode.cs @@ -3,16 +3,17 @@ using System.Configuration; using System.Linq; using MetaCode.Compiler.AbstractTree.Expressions; +using MetaCode.Compiler.Commons; namespace MetaCode.Compiler.AbstractTree.Statements { public class ForeachLoopStatementNode : LoopStatementNodeBase { - public Variable LoopVariable { get; protected set; } + public VariableDeclaration LoopVariable { get; protected set; } public ExpressionNode Expression { get; protected set; } - public ForeachLoopStatementNode(Variable loopVariable, ExpressionNode expression, StatementNodeBase body) + public ForeachLoopStatementNode(VariableDeclaration loopVariable, ExpressionNode expression, StatementNodeBase body) : base(body) { if (loopVariable == null) throw new ArgumentNullException("loopVariable", "The loopVariable is null!"); @@ -21,12 +22,12 @@ public ForeachLoopStatementNode(Variable loopVariable, ExpressionNode expression LoopVariable = loopVariable; Expression = expression; - AddChildren(loopVariable, expression, body); + AddChildren(expression, body); } public override IEnumerable Children { - get { return base.Children.Concat(new Node[] { LoopVariable, Expression }); } + get { return base.Children.Concat(new Node[] { Expression }); } } } } \ No newline at end of file diff --git a/project/MetaCode/MetaCode.Compiler/AbstractTree/Statements/IfStatementNode.cs b/project/MetaCode/MetaCode.Compiler/AbstractTree/Statements/IfStatementNode.cs index c59ad0e..fcd81fe 100644 --- a/project/MetaCode/MetaCode.Compiler/AbstractTree/Statements/IfStatementNode.cs +++ b/project/MetaCode/MetaCode.Compiler/AbstractTree/Statements/IfStatementNode.cs @@ -11,17 +11,17 @@ public class IfStatementNode : StatementNodeBase { public ExpressionNode ConditionExpression { get; protected set; } - public StatementNodeBase TrueStatementNode { get; protected set; } + public BlockStatementNode TrueStatementNode { get; protected set; } - public StatementNodeBase FalseStatementNode { get; protected set; } + public BlockStatementNode FalseStatementNode { get; protected set; } - public IfStatementNode(ExpressionNode condition, StatementNodeBase trueStatement, StatementNodeBase falseStatement) + public IfStatementNode(ExpressionNode condition, BlockStatementNode trueStatement, BlockStatementNode falseStatement) { if (condition == null) throw new ArgumentNullException("condition"); if (trueStatement == null) throw new ArgumentNullException("trueStatement"); if (falseStatement == null) throw new ArgumentNullException("falseStatement"); - if (condition.Type == typeof(bool)) + if (condition.Type != typeof(bool)) throw new ArgumentException("The type of condition is not boolean!"); ConditionExpression = condition; @@ -30,13 +30,5 @@ public IfStatementNode(ExpressionNode condition, StatementNodeBase trueStatement AddChildren(condition, trueStatement, falseStatement); } - - public override IEnumerable Children - { - get - { - return base.Children.Concat(new Node[] { ConditionExpression, TrueStatementNode, FalseStatementNode }); - } - } } } diff --git a/project/MetaCode/MetaCode.Compiler/AbstractTree/Statements/VariableDeclarationStatementNode.cs b/project/MetaCode/MetaCode.Compiler/AbstractTree/Statements/VariableDeclarationStatementNode.cs index 7cbd6d5..76797c0 100644 --- a/project/MetaCode/MetaCode.Compiler/AbstractTree/Statements/VariableDeclarationStatementNode.cs +++ b/project/MetaCode/MetaCode.Compiler/AbstractTree/Statements/VariableDeclarationStatementNode.cs @@ -32,6 +32,8 @@ public VariableDeclarationStatementNode(VariableDeclaration declaration, Express Declaration = declaration; InitialValue = initialValue; + + AddChildren(InitialValue); } } } diff --git a/project/MetaCode/MetaCode.Compiler/AbstractTree/Visitors/TreeVisitorBase.cs b/project/MetaCode/MetaCode.Compiler/AbstractTree/Visitors/TreeVisitorBase.cs new file mode 100644 index 0000000..6724581 --- /dev/null +++ b/project/MetaCode/MetaCode.Compiler/AbstractTree/Visitors/TreeVisitorBase.cs @@ -0,0 +1,79 @@ +using Antlr4.Runtime.Tree; +using MetaCode.Core; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MetaCode.Compiler.AbstractTree.Visitors +{ + public class TreeVisitorBase + { + public delegate TResult VisitorDelegate(TreeVisitorBase visitor, Node node); + + public delegate TResult VisitorDelegate(TreeVisitorBase visitor, TNode node) where TNode : Node; + + protected readonly Dictionary _visitors; + + protected VisitorDelegate _defaultVisitor; + + public TreeVisitorBase() + { + _visitors = new Dictionary(); + InitDefaultVisitor(); + } + + private void InitDefaultVisitor() + { + _defaultVisitor = (visitor, node) => + { + foreach (var child in node.Children) + visitor.VisitChild(child); + + return default(TResult); + }; + } + + public TResult VisitChild(Node node) + { + if (node == null) + ThrowHelper.ThrowArgumentNullException(() => node); + + var type = node.GetType(); + VisitorDelegate visitor = null; + + if (_visitors.TryGetValue(type, out visitor)) + return visitor(this, node); + + return _defaultVisitor(this, node); + } + + public TreeVisitorBase DefaultVisitor(VisitorDelegate func) + { + if (func == null) + ThrowHelper.ThrowArgumentNullException(() => func); + + _defaultVisitor = func; + return this; + } + + public TreeVisitorBase If(VisitorDelegate func) + where TNode : Node + { + if (func == null) + ThrowHelper.ThrowArgumentNullException(() => func); + + _visitors.Add(typeof(TNode), (visitor, node) => func(this, node as TNode)); + + return this; + } + + public TreeVisitorBase Clear() + { + _visitors.Clear(); + InitDefaultVisitor(); + return this; + } + } +} diff --git a/project/MetaCode/MetaCode.Compiler/Commons/FunctionDeclaration.cs b/project/MetaCode/MetaCode.Compiler/Commons/FunctionDeclaration.cs index be89dad..e63c410 100644 --- a/project/MetaCode/MetaCode.Compiler/Commons/FunctionDeclaration.cs +++ b/project/MetaCode/MetaCode.Compiler/Commons/FunctionDeclaration.cs @@ -9,12 +9,10 @@ public class FunctionDeclaration : DeclarationBase public Type ReturnType { get; protected set; } - public string Name { get; protected set; } - - public FunctionDeclaration(string name, Type returnType, Scope scope) : this(name, returnType, new Type[0], scope) { + } public FunctionDeclaration(string name, Type returnType, Type[] formalParameters, Scope scope) diff --git a/project/MetaCode/MetaCode.Compiler/Commons/Scope.cs b/project/MetaCode/MetaCode.Compiler/Commons/Scope.cs index d00b296..5c233ae 100644 --- a/project/MetaCode/MetaCode.Compiler/Commons/Scope.cs +++ b/project/MetaCode/MetaCode.Compiler/Commons/Scope.cs @@ -87,5 +87,13 @@ public VariableDeclaration DeclareVariable(string name, Type type) return declaration; } + + public FunctionDeclaration DeclareFunction(string name, Type returnType, Type[] parameters) + { + var declaration = new FunctionDeclaration(name, returnType, parameters, this); + _functionDeclarations.Add(declaration); + + return declaration; + } } } diff --git a/project/MetaCode/MetaCode.Compiler/Grammar/MetaCode.g4 b/project/MetaCode/MetaCode.Compiler/Grammar/MetaCode.g4 index 5313412..2395659 100644 --- a/project/MetaCode/MetaCode.Compiler/Grammar/MetaCode.g4 +++ b/project/MetaCode/MetaCode.Compiler/Grammar/MetaCode.g4 @@ -7,6 +7,7 @@ statements : (statement ';')+ ; statement : Expression=expression + | Return=returnStatement | Attributes=attributes? VariableDeclaration=variableDeclaration | Attributes=attributes? If=ifStatement | Attributes=attributes? Block=blockStatement @@ -36,24 +37,26 @@ expression : PrimaryExpression=primaryExpression | Left=expression Operator=OR Right=expression ; -functionCallExpression : primaryExpression '(' expression? ')' +functionCallExpression : (ID | functionExpression) '(' expression? ')' ; -memberExpression : primaryExpression ('.' (identifier | functionCallExpression))+ +memberExpression : (primaryExpression | functionCallExpression) ('.' memberTagExpression)+ ; +memberTagExpression : ID | functionCallExpression; + primaryExpression : Attributes=attributes? Constant=constant - | Attributes=attributes? Id=identifier + | Attributes=attributes? Id=ID | Attributes=attributes? Function=functionExpression | Attributes=attributes? Assignment=assignmentExpression | Attributes=attributes? '(' InnerExpression=expression ')' ; -functionExpression : FUNCTION FunctionName=identifier? '(' Parameters=formalParameterList? ')' (':' ReturnType=typeName)? DO BodyStatements=statements END - | FUNCTION FunctionName=identifier? '(' Parameters=formalParameterList? ')' (':' ReturnType=typeName)? '=' BodyExpression=expression +functionExpression : FUNCTION FunctionName=ID? '(' Parameters=formalParameterList? ')' (':' ReturnType=typeName) DO BodyStatements=statements END + | FUNCTION FunctionName=ID? '(' Parameters=formalParameterList? ')' (':' ReturnType=typeName) '=' BodyExpression=expression ; -foreachStatement : FOREACH '(' Var=VAR? Id=identifier (':' VariableType=typeName)? IN ArrayExpression=expression ')' Body=statement +foreachStatement : FOREACH '(' Var=VAR? Id=ID (':' VariableType=typeName)? IN ArrayExpression=expression ')' Body=statement ; whileStatement : WHILE '(' ConditionExpression=expression ')' Body=statement @@ -64,22 +67,24 @@ blockStatement : DO Body=statements END skipStatement : SKIP; -assignmentExpression: Variable=identifier ASSIGN Value=expression (ConditionalAttributes=attributes? IF '(' ConditionalExpression=expression ')')? +returnStatement : RETURN expression; + +assignmentExpression: Variable=ID ASSIGN Value=expression (ConditionalAttributes=attributes? IF '(' ConditionalExpression=expression ')')? ; ifStatement : IF '(' Condition=expression ')' Statements=statements - ElseIfExpressions=elseIfStatement* + ElseIfStatements=elseIfStatement* (ELSE ElseStatements=statements)? END ; -elseIfStatement : ELSE IF '(' expression ')' statements +elseIfStatement : ELSE IF '(' Condition=expression ')' Statements=statements ; formalParameterList : formalParameter (',' formalParameter)* ; -formalParameter : attributes? identifier ':' typeName +formalParameter : Attributes=attributes? Name=ID ':' Type=typeName ; actualParameterList : expression (',' expression)* @@ -95,9 +100,6 @@ constant : Number=numberConstant | Interval=intervalConstant ; -identifier : Id=ID - ; - numberConstant : NUMBER; stringConstant : STRING; @@ -155,6 +157,8 @@ NOT : 'not'; NULL : 'null'; +RETURN : 'return'; + LEFT_PARENTHESIS : '('; RIGHT_PARENTHESIS : ')'; diff --git a/project/MetaCode/MetaCode.Compiler/Helpers/TypeHelper.cs b/project/MetaCode/MetaCode.Compiler/Helpers/TypeHelper.cs index 69d8b78..12c5edb 100644 --- a/project/MetaCode/MetaCode.Compiler/Helpers/TypeHelper.cs +++ b/project/MetaCode/MetaCode.Compiler/Helpers/TypeHelper.cs @@ -67,12 +67,23 @@ public static IEnumerable GetClassHierarchy(this Type type) Type result = type; - do { + do + { yield return result; result = result.BaseType; } while (result != null && !result.IsInterface); } + public static Type GetItemType(this Type enumerable) + { + var enumerableType = enumerable.GetInterfaces() + .Where(type => type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>)) + .Select(type => type.GenericTypeArguments[0]) + .FirstOrDefault(); + + return enumerableType; + } + public static Type FindType(string typeName) { if (string.IsNullOrWhiteSpace(typeName)) diff --git a/project/MetaCode/MetaCode.Compiler/MetaCode.Compiler.csproj b/project/MetaCode/MetaCode.Compiler/MetaCode.Compiler.csproj index e5e4852..09688d2 100644 --- a/project/MetaCode/MetaCode.Compiler/MetaCode.Compiler.csproj +++ b/project/MetaCode/MetaCode.Compiler/MetaCode.Compiler.csproj @@ -85,7 +85,7 @@ - + @@ -98,6 +98,7 @@ + diff --git a/project/MetaCode/MetaCode.Compiler/Services/CompilerService.cs b/project/MetaCode/MetaCode.Compiler/Services/CompilerService.cs index 2668a11..214e4b1 100644 --- a/project/MetaCode/MetaCode.Compiler/Services/CompilerService.cs +++ b/project/MetaCode/MetaCode.Compiler/Services/CompilerService.cs @@ -4,6 +4,7 @@ using System.Text; using System.Threading.Tasks; using Antlr4.Runtime.Dfa; +using MetaCode.Compiler.AbstractTree.Factories; using MetaCode.Compiler.AbstractTree.Statements; using MetaCode.Compiler.Commons; using MetaCode.Core; @@ -19,6 +20,12 @@ public class CompilerService public List Warnings { get; protected set; } + public ExpressionFactory ExpressionFactory { get; internal set; } + + public StatementFactory StatementFactory { get; internal set; } + + public ConstantLiteralFactory ConstantLiteralFactory { get; internal set; } + public CompilerService() { Errors = new List(); diff --git a/project/MetaCode/MetaCode.Compiler/Visitors/AbstractTreeVisitor.Constants.cs b/project/MetaCode/MetaCode.Compiler/Visitors/AbstractTreeVisitor.Constants.cs index bc2c182..898575d 100644 --- a/project/MetaCode/MetaCode.Compiler/Visitors/AbstractTreeVisitor.Constants.cs +++ b/project/MetaCode/MetaCode.Compiler/Visitors/AbstractTreeVisitor.Constants.cs @@ -62,14 +62,8 @@ public override Node VisitConstant(MetaCodeParser.ConstantContext context) #endregion - #region Identifier Visitor method + #region variable Visitor method - public override Node VisitIdentifier(MetaCodeParser.IdentifierContext context) - { - var id = context.Id.Text; - - return ExpressionFactory.Identifier(id); - } #endregion diff --git a/project/MetaCode/MetaCode.Compiler/Visitors/AbstractTreeVisitor.Expressions.cs b/project/MetaCode/MetaCode.Compiler/Visitors/AbstractTreeVisitor.Expressions.cs index b5d3df9..db8cb17 100644 --- a/project/MetaCode/MetaCode.Compiler/Visitors/AbstractTreeVisitor.Expressions.cs +++ b/project/MetaCode/MetaCode.Compiler/Visitors/AbstractTreeVisitor.Expressions.cs @@ -12,6 +12,7 @@ using MetaCode.Compiler.AbstractTree.Constants; using MetaCode.Compiler.AbstractTree.Expressions; using MetaCode.Compiler.AbstractTree.Factories; +using MetaCode.Compiler.AbstractTree.Statements; using MetaCode.Compiler.Grammar; using MetaCode.Core; @@ -37,13 +38,47 @@ private Node VisitBinaryOperands(MetaCodeParser.ExpressionContext left, MetaCode public override Node VisitExpression(MetaCodeParser.ExpressionContext context) { var result = GetNodeFromContext(context.PrimaryExpression, - context.FunctionCallExpression, - context.MemberExpression) ?? + context.FunctionCallExpression, + context.MemberExpression) ?? VisitBinaryOperands(context.Left, context.Right, context.Operator); return result; } + public override Node VisitFunctionExpression(MetaCodeParser.FunctionExpressionContext context) + { + var identifier = context.FunctionName.With(functionName => functionName.Text); + var returnType = context.ReturnType.Accept(this) as TypeNameNode; + + var parameters = context.Parameters; + + var formalParameters = new Func(() => + { + if (parameters == null) + return new FunctionParameterNode[0]; + + var result = parameters.formalParameter().Select(param => + { + return ExpressionFactory.FunctionFormalParameter(param.Name.Text, + param.Type.With(type => type.Accept(this) as TypeNameNode), + null); + }); + + return result.ToArray(); + })(); + + if (context.BodyExpression != null) + return ExpressionFactory.Function(identifier, + returnType.Type, + formalParameters, + new Lazy(() => context.BodyExpression.Accept(this) as ExpressionNode)); + + return ExpressionFactory.Function(identifier, + returnType.Type, + formalParameters, + new Lazy(() => context.BodyStatements.Accept(this) as BlockStatementNode)); + } + public override Node VisitPrimaryExpression(MetaCodeParser.PrimaryExpressionContext context) { if (context.Constant != null) @@ -51,15 +86,33 @@ public override Node VisitPrimaryExpression(MetaCodeParser.PrimaryExpressionCont var constant = context.Constant.Accept(this) as ConstantLiteralNode; return ExpressionFactory.Constant(constant); } - + if (context.Id != null) + return ExpressionFactory.Identifier(context.Id.Text); + + if (context.Assignment != null) { - var id = context.Id.Accept(this); - //ExpressionFactory.Identifier(id); + var assignment = context.Assignment; } + if (context.InnerExpression != null) + return context.InnerExpression.Accept(this); + return base.VisitPrimaryExpression(context); } + + public override Node VisitMemberExpression(MetaCodeParser.MemberExpressionContext context) + { + var primary = context.primaryExpression().With(expression => expression.Accept(this)) ?? + context.functionCallExpression().With(expression => expression.Accept(this)); + + var tags = new[] { primary }.Concat(context.memberTagExpression() + .Select(tag => tag.functionCallExpression() + .With(expression => expression.Accept(this)))) + .ToArray(); + + return ExpressionFactory.Member(tags.Cast()); + } } } diff --git a/project/MetaCode/MetaCode.Compiler/Visitors/AbstractTreeVisitor.Statements.cs b/project/MetaCode/MetaCode.Compiler/Visitors/AbstractTreeVisitor.Statements.cs index a7c03d2..c133347 100644 --- a/project/MetaCode/MetaCode.Compiler/Visitors/AbstractTreeVisitor.Statements.cs +++ b/project/MetaCode/MetaCode.Compiler/Visitors/AbstractTreeVisitor.Statements.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Net.Mime; +using System.Net.Sockets; using System.Text; using System.Threading.Tasks; using Antlr4.Runtime; @@ -32,11 +33,20 @@ public override Node VisitStatement(MetaCodeParser.StatementContext context) public override Node VisitIfStatement(MetaCodeParser.IfStatementContext context) { var condition = context.Condition.Accept(this) as ExpressionNode; + var statements = context.Statements.Accept(this) as BlockStatementNode; + var elseIfStatements = context.elseIfStatement() + .Select(elseIf => elseIf.Accept(this) as IfStatementNode) + .ToArray(); + var elseStatements = context.ElseStatements.With(statement => statement.Accept(this)) as BlockStatementNode; - if (condition == null) - throw new Exception("The condition is null!"); + return StatementFactory.IfThenElse(condition, statements, elseIfStatements, elseStatements); + } - return base.VisitIfStatement(context); + public override Node VisitElseIfStatement(MetaCodeParser.ElseIfStatementContext context) + { + var condition = context.Condition.Accept(this) as ExpressionNode; + var statements = context.Statements.Accept(this) as BlockStatementNode; + return StatementFactory.IfThenElse(condition, statements); } public override Node VisitSkipStatement(MetaCodeParser.SkipStatementContext context) @@ -47,9 +57,9 @@ public override Node VisitSkipStatement(MetaCodeParser.SkipStatementContext cont public override Node VisitStatements(MetaCodeParser.StatementsContext context) { return StatementFactory.Block( - () => context.statement() + new Lazy(() => context.statement() .Select(statement => statement.Accept(this) as StatementNodeBase) - .ToArray() + .ToArray()) ); } @@ -74,10 +84,15 @@ public override Node VisitWhileStatement(MetaCodeParser.WhileStatementContext co public override Node VisitForeachStatement(MetaCodeParser.ForeachStatementContext context) { + var identifier = context.Id.Text; var arrayExpression = context.ArrayExpression.Accept(this) as ExpressionNode; - var body = context.Body.Accept(this) as StatementNodeBase; + var variableType = context.VariableType.With(variable => variable.Accept(this)) as TypeNameNode; - return base.VisitForeachStatement(context); + return StatementFactory.Foreach(identifier, + variableType, + arrayExpression, + new Lazy(() => context.Body.Accept(this) as StatementNodeBase), + context.Var != null); } public override Node VisitVariableDeclaration(MetaCodeParser.VariableDeclarationContext context) @@ -88,5 +103,11 @@ public override Node VisitVariableDeclaration(MetaCodeParser.VariableDeclaration return StatementFactory.DeclareVariable(name, typeName, defaultValue); } + + public override Node VisitInit(MetaCodeParser.InitContext context) + { + var result = base.VisitInit(context); + return result; + } } } diff --git a/project/MetaCode/MetaCode.Compiler/bin/Debug/MetaCode.Compiler.dll b/project/MetaCode/MetaCode.Compiler/bin/Debug/MetaCode.Compiler.dll index 12d8c29..bd9598f 100644 Binary files a/project/MetaCode/MetaCode.Compiler/bin/Debug/MetaCode.Compiler.dll and b/project/MetaCode/MetaCode.Compiler/bin/Debug/MetaCode.Compiler.dll differ diff --git a/project/MetaCode/MetaCode.Compiler/bin/Debug/MetaCode.Compiler.pdb b/project/MetaCode/MetaCode.Compiler/bin/Debug/MetaCode.Compiler.pdb index 01889c7..a7ead44 100644 Binary files a/project/MetaCode/MetaCode.Compiler/bin/Debug/MetaCode.Compiler.pdb and b/project/MetaCode/MetaCode.Compiler/bin/Debug/MetaCode.Compiler.pdb differ diff --git a/project/MetaCode/MetaCode.Compiler/bin/Debug/MetaCode.Core.dll b/project/MetaCode/MetaCode.Compiler/bin/Debug/MetaCode.Core.dll index 1e0094a..97dba1b 100644 Binary files a/project/MetaCode/MetaCode.Compiler/bin/Debug/MetaCode.Core.dll and b/project/MetaCode/MetaCode.Compiler/bin/Debug/MetaCode.Core.dll differ diff --git a/project/MetaCode/MetaCode.Compiler/bin/Debug/MetaCode.Core.pdb b/project/MetaCode/MetaCode.Compiler/bin/Debug/MetaCode.Core.pdb index 5f99b8d..ebdaef2 100644 Binary files a/project/MetaCode/MetaCode.Compiler/bin/Debug/MetaCode.Core.pdb and b/project/MetaCode/MetaCode.Compiler/bin/Debug/MetaCode.Core.pdb differ diff --git a/project/MetaCode/MetaCode.Compiler/obj/Debug/DesignTimeResolveAssemblyReferencesInput.cache b/project/MetaCode/MetaCode.Compiler/obj/Debug/DesignTimeResolveAssemblyReferencesInput.cache index 7aa6e8a..eccedb5 100644 Binary files a/project/MetaCode/MetaCode.Compiler/obj/Debug/DesignTimeResolveAssemblyReferencesInput.cache and b/project/MetaCode/MetaCode.Compiler/obj/Debug/DesignTimeResolveAssemblyReferencesInput.cache differ diff --git a/project/MetaCode/MetaCode.Compiler/obj/Debug/MetaCode.Compiler.csproj.Antlr4GeneratedCodeFileListAbsolute.txt b/project/MetaCode/MetaCode.Compiler/obj/Debug/MetaCode.Compiler.csproj.Antlr4GeneratedCodeFileListAbsolute.txt index 815a086..b76c73a 100644 --- a/project/MetaCode/MetaCode.Compiler/obj/Debug/MetaCode.Compiler.csproj.Antlr4GeneratedCodeFileListAbsolute.txt +++ b/project/MetaCode/MetaCode.Compiler/obj/Debug/MetaCode.Compiler.csproj.Antlr4GeneratedCodeFileListAbsolute.txt @@ -1,6 +1,6 @@ -E:\Development\Projects\C#\MetaCode\project\MetaCode\MetaCode.Compiler\obj\Debug\MetaCodeLexer.cs -E:\Development\Projects\C#\MetaCode\project\MetaCode\MetaCode.Compiler\obj\Debug\MetaCodeParser.cs -E:\Development\Projects\C#\MetaCode\project\MetaCode\MetaCode.Compiler\obj\Debug\MetaCodeListener.cs -E:\Development\Projects\C#\MetaCode\project\MetaCode\MetaCode.Compiler\obj\Debug\MetaCodeBaseListener.cs -E:\Development\Projects\C#\MetaCode\project\MetaCode\MetaCode.Compiler\obj\Debug\MetaCodeVisitor.cs -E:\Development\Projects\C#\MetaCode\project\MetaCode\MetaCode.Compiler\obj\Debug\MetaCodeBaseVisitor.cs +E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler\obj\Debug\MetaCodeLexer.cs +E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler\obj\Debug\MetaCodeParser.cs +E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler\obj\Debug\MetaCodeListener.cs +E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler\obj\Debug\MetaCodeBaseListener.cs +E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler\obj\Debug\MetaCodeVisitor.cs +E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler\obj\Debug\MetaCodeBaseVisitor.cs diff --git a/project/MetaCode/MetaCode.Compiler/obj/Debug/MetaCode.Compiler.csproj.FileListAbsolute.txt b/project/MetaCode/MetaCode.Compiler/obj/Debug/MetaCode.Compiler.csproj.FileListAbsolute.txt index c4aaece..bb25862 100644 --- a/project/MetaCode/MetaCode.Compiler/obj/Debug/MetaCode.Compiler.csproj.FileListAbsolute.txt +++ b/project/MetaCode/MetaCode.Compiler/obj/Debug/MetaCode.Compiler.csproj.FileListAbsolute.txt @@ -1,19 +1,3 @@ -E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler\obj\Debug\MetaCodeLexer.cs -E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler\obj\Debug\MetaCodeParser.cs -E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler\obj\Debug\MetaCodeListener.cs -E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler\obj\Debug\MetaCodeBaseListener.cs -E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler\obj\Debug\MetaCodeVisitor.cs -E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler\obj\Debug\MetaCodeBaseVisitor.cs -E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler\obj\Debug\MetaCode.Compiler.csproj.Antlr4GeneratedCodeFileListAbsolute.txt -E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler\bin\Debug\MetaCode.Compiler.dll -E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler\bin\Debug\MetaCode.Compiler.pdb -E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler\bin\Debug\Antlr4.Runtime.v4.5.dll -E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler\bin\Debug\Antlr4.Runtime.v4.5.xml -E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler\obj\Debug\MetaCode.Compiler.dll -E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler\obj\Debug\MetaCode.Compiler.pdb -E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler\bin\Debug\MetaCode.Core.dll -E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler\bin\Debug\MetaCode.Core.pdb -E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler\obj\Debug\MetaCode.Compiler.csprojResolveAssemblyReference.cache E:\Development\Projects\C#\MetaCode\project\MetaCode\MetaCode.Compiler\bin\Debug\MetaCode.Compiler.dll E:\Development\Projects\C#\MetaCode\project\MetaCode\MetaCode.Compiler\bin\Debug\MetaCode.Compiler.pdb E:\Development\Projects\C#\MetaCode\project\MetaCode\MetaCode.Compiler\bin\Debug\Antlr4.Runtime.v4.5.dll @@ -30,3 +14,19 @@ E:\Development\Projects\C#\MetaCode\project\MetaCode\MetaCode.Compiler\obj\Debug E:\Development\Projects\C#\MetaCode\project\MetaCode\MetaCode.Compiler\obj\Debug\MetaCode.Compiler.csproj.Antlr4GeneratedCodeFileListAbsolute.txt E:\Development\Projects\C#\MetaCode\project\MetaCode\MetaCode.Compiler\obj\Debug\MetaCode.Compiler.dll E:\Development\Projects\C#\MetaCode\project\MetaCode\MetaCode.Compiler\obj\Debug\MetaCode.Compiler.pdb +E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler\bin\Debug\MetaCode.Compiler.dll +E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler\bin\Debug\MetaCode.Compiler.pdb +E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler\bin\Debug\Antlr4.Runtime.v4.5.dll +E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler\bin\Debug\MetaCode.Core.dll +E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler\bin\Debug\MetaCode.Core.pdb +E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler\bin\Debug\Antlr4.Runtime.v4.5.xml +E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler\obj\Debug\MetaCode.Compiler.csprojResolveAssemblyReference.cache +E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler\obj\Debug\MetaCodeLexer.cs +E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler\obj\Debug\MetaCodeParser.cs +E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler\obj\Debug\MetaCodeListener.cs +E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler\obj\Debug\MetaCodeBaseListener.cs +E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler\obj\Debug\MetaCodeVisitor.cs +E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler\obj\Debug\MetaCodeBaseVisitor.cs +E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler\obj\Debug\MetaCode.Compiler.csproj.Antlr4GeneratedCodeFileListAbsolute.txt +E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler\obj\Debug\MetaCode.Compiler.dll +E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler\obj\Debug\MetaCode.Compiler.pdb diff --git a/project/MetaCode/MetaCode.Compiler/obj/Debug/MetaCode.Compiler.csprojResolveAssemblyReference.cache b/project/MetaCode/MetaCode.Compiler/obj/Debug/MetaCode.Compiler.csprojResolveAssemblyReference.cache index 3df788a..a977018 100644 Binary files a/project/MetaCode/MetaCode.Compiler/obj/Debug/MetaCode.Compiler.csprojResolveAssemblyReference.cache and b/project/MetaCode/MetaCode.Compiler/obj/Debug/MetaCode.Compiler.csprojResolveAssemblyReference.cache differ diff --git a/project/MetaCode/MetaCode.Compiler/obj/Debug/MetaCode.Compiler.dll b/project/MetaCode/MetaCode.Compiler/obj/Debug/MetaCode.Compiler.dll index 12d8c29..bd9598f 100644 Binary files a/project/MetaCode/MetaCode.Compiler/obj/Debug/MetaCode.Compiler.dll and b/project/MetaCode/MetaCode.Compiler/obj/Debug/MetaCode.Compiler.dll differ diff --git a/project/MetaCode/MetaCode.Compiler/obj/Debug/MetaCode.Compiler.pdb b/project/MetaCode/MetaCode.Compiler/obj/Debug/MetaCode.Compiler.pdb index 01889c7..a7ead44 100644 Binary files a/project/MetaCode/MetaCode.Compiler/obj/Debug/MetaCode.Compiler.pdb and b/project/MetaCode/MetaCode.Compiler/obj/Debug/MetaCode.Compiler.pdb differ diff --git a/project/MetaCode/MetaCode.Compiler/obj/Debug/MetaCode.tokens b/project/MetaCode/MetaCode.Compiler/obj/Debug/MetaCode.tokens index 7b17149..e763b7a 100644 --- a/project/MetaCode/MetaCode.Compiler/obj/Debug/MetaCode.tokens +++ b/project/MetaCode/MetaCode.Compiler/obj/Debug/MetaCode.tokens @@ -2,12 +2,12 @@ FUNCTION=19 WHILE=21 NULL=34 ELSE=23 -NUMBER=42 -ATTRIBUTE_ID=40 -WHITESPACE=43 +NUMBER=43 +ATTRIBUTE_ID=41 +WHITESPACE=44 DO=24 NOT=33 -ID=37 +ID=38 AND=31 T__9=9 T__8=10 @@ -17,31 +17,32 @@ T__5=13 IF=22 SKIP=27 T__4=14 -MULTILINE_COMMENT=39 +MULTILINE_COMMENT=40 BOOLEAN=26 T__16=2 IN=29 T__15=3 -NEWLINE=44 -RIGHT_PARENTHESIS=36 +NEWLINE=45 +RIGHT_PARENTHESIS=37 T__17=1 T__12=6 T__11=7 T__14=4 -LEFT_PARENTHESIS=35 +LEFT_PARENTHESIS=36 T__13=5 T__1=17 T__0=18 OR=32 T__10=8 T__3=15 +RETURN=35 ASSIGN=30 T__2=16 VAR=28 FOREACH=20 -COMMENT=38 +COMMENT=39 END=25 -STRING=41 +STRING=42 'foreach'=20 'end'=25 '>='=18 @@ -50,9 +51,10 @@ STRING=41 'null'=34 '>'=14 ';'=11 +'return'=35 '='=30 '+'=4 -')'=36 +')'=37 '.'=2 'function'=19 'do'=24 @@ -66,7 +68,7 @@ STRING=41 '!='=10 '<'=9 'if'=22 -'('=35 +'('=36 'not'=33 ':'=8 'or'=32 diff --git a/project/MetaCode/MetaCode.Compiler/obj/Debug/MetaCodeBaseListener.cs b/project/MetaCode/MetaCode.Compiler/obj/Debug/MetaCodeBaseListener.cs index 164ae67..ae316f7 100644 --- a/project/MetaCode/MetaCode.Compiler/obj/Debug/MetaCodeBaseListener.cs +++ b/project/MetaCode/MetaCode.Compiler/obj/Debug/MetaCodeBaseListener.cs @@ -8,7 +8,7 @@ // //------------------------------------------------------------------------------ -// Generated from E:\Development\Projects\C#\MetaCode\project\MetaCode\MetaCode.Compiler\Grammar\MetaCode.g4 by ANTLR 4.2-SNAPSHOT +// Generated from E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler\Grammar\MetaCode.g4 by ANTLR 4.2-SNAPSHOT // Unreachable code detected #pragma warning disable 0162 @@ -59,6 +59,19 @@ public virtual void EnterFormalParameter([NotNull] MetaCodeParser.FormalParamete /// The parse tree. public virtual void ExitFormalParameter([NotNull] MetaCodeParser.FormalParameterContext context) { } + /// + /// Enter a parse tree produced by . + /// The default implementation does nothing. + /// + /// The parse tree. + public virtual void EnterReturnStatement([NotNull] MetaCodeParser.ReturnStatementContext context) { } + /// + /// Exit a parse tree produced by . + /// The default implementation does nothing. + /// + /// The parse tree. + public virtual void ExitReturnStatement([NotNull] MetaCodeParser.ReturnStatementContext context) { } + /// /// Enter a parse tree produced by . /// The default implementation does nothing. @@ -228,6 +241,19 @@ public virtual void EnterBooleanConstant([NotNull] MetaCodeParser.BooleanConstan /// The parse tree. public virtual void ExitBooleanConstant([NotNull] MetaCodeParser.BooleanConstantContext context) { } + /// + /// Enter a parse tree produced by . + /// The default implementation does nothing. + /// + /// The parse tree. + public virtual void EnterMemberTagExpression([NotNull] MetaCodeParser.MemberTagExpressionContext context) { } + /// + /// Exit a parse tree produced by . + /// The default implementation does nothing. + /// + /// The parse tree. + public virtual void ExitMemberTagExpression([NotNull] MetaCodeParser.MemberTagExpressionContext context) { } + /// /// Enter a parse tree produced by . /// The default implementation does nothing. @@ -384,19 +410,6 @@ public virtual void EnterForeachStatement([NotNull] MetaCodeParser.ForeachStatem /// The parse tree. public virtual void ExitForeachStatement([NotNull] MetaCodeParser.ForeachStatementContext context) { } - /// - /// Enter a parse tree produced by . - /// The default implementation does nothing. - /// - /// The parse tree. - public virtual void EnterIdentifier([NotNull] MetaCodeParser.IdentifierContext context) { } - /// - /// Exit a parse tree produced by . - /// The default implementation does nothing. - /// - /// The parse tree. - public virtual void ExitIdentifier([NotNull] MetaCodeParser.IdentifierContext context) { } - /// /// Enter a parse tree produced by . /// The default implementation does nothing. diff --git a/project/MetaCode/MetaCode.Compiler/obj/Debug/MetaCodeBaseVisitor.cs b/project/MetaCode/MetaCode.Compiler/obj/Debug/MetaCodeBaseVisitor.cs index b871e38..26b342d 100644 --- a/project/MetaCode/MetaCode.Compiler/obj/Debug/MetaCodeBaseVisitor.cs +++ b/project/MetaCode/MetaCode.Compiler/obj/Debug/MetaCodeBaseVisitor.cs @@ -8,7 +8,7 @@ // //------------------------------------------------------------------------------ -// Generated from E:\Development\Projects\C#\MetaCode\project\MetaCode\MetaCode.Compiler\Grammar\MetaCode.g4 by ANTLR 4.2-SNAPSHOT +// Generated from E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler\Grammar\MetaCode.g4 by ANTLR 4.2-SNAPSHOT // Unreachable code detected #pragma warning disable 0162 @@ -54,6 +54,17 @@ public partial class MetaCodeBaseVisitor : AbstractParseTreeVisitorThe visitor result. public virtual Result VisitFormalParameter([NotNull] MetaCodeParser.FormalParameterContext context) { return VisitChildren(context); } + /// + /// Visit a parse tree produced by . + /// + /// The default implementation returns the result of calling + /// on . + /// + /// + /// The parse tree. + /// The visitor result. + public virtual Result VisitReturnStatement([NotNull] MetaCodeParser.ReturnStatementContext context) { return VisitChildren(context); } + /// /// Visit a parse tree produced by . /// @@ -197,6 +208,17 @@ public partial class MetaCodeBaseVisitor : AbstractParseTreeVisitorThe visitor result. public virtual Result VisitBooleanConstant([NotNull] MetaCodeParser.BooleanConstantContext context) { return VisitChildren(context); } + /// + /// Visit a parse tree produced by . + /// + /// The default implementation returns the result of calling + /// on . + /// + /// + /// The parse tree. + /// The visitor result. + public virtual Result VisitMemberTagExpression([NotNull] MetaCodeParser.MemberTagExpressionContext context) { return VisitChildren(context); } + /// /// Visit a parse tree produced by . /// @@ -329,17 +351,6 @@ public partial class MetaCodeBaseVisitor : AbstractParseTreeVisitorThe visitor result. public virtual Result VisitForeachStatement([NotNull] MetaCodeParser.ForeachStatementContext context) { return VisitChildren(context); } - /// - /// Visit a parse tree produced by . - /// - /// The default implementation returns the result of calling - /// on . - /// - /// - /// The parse tree. - /// The visitor result. - public virtual Result VisitIdentifier([NotNull] MetaCodeParser.IdentifierContext context) { return VisitChildren(context); } - /// /// Visit a parse tree produced by . /// diff --git a/project/MetaCode/MetaCode.Compiler/obj/Debug/MetaCodeLexer.cs b/project/MetaCode/MetaCode.Compiler/obj/Debug/MetaCodeLexer.cs index fb0f092..3ec9c39 100644 --- a/project/MetaCode/MetaCode.Compiler/obj/Debug/MetaCodeLexer.cs +++ b/project/MetaCode/MetaCode.Compiler/obj/Debug/MetaCodeLexer.cs @@ -8,7 +8,7 @@ // //------------------------------------------------------------------------------ -// Generated from E:\Development\Projects\C#\MetaCode\project\MetaCode\MetaCode.Compiler\Grammar\MetaCode.g4 by ANTLR 4.2-SNAPSHOT +// Generated from E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler\Grammar\MetaCode.g4 by ANTLR 4.2-SNAPSHOT // Unreachable code detected #pragma warning disable 0162 @@ -31,9 +31,9 @@ public const int T__9=9, T__8=10, T__7=11, T__6=12, T__5=13, T__4=14, T__3=15, T__2=16, T__1=17, T__0=18, FUNCTION=19, FOREACH=20, WHILE=21, IF=22, ELSE=23, DO=24, END=25, BOOLEAN=26, SKIP=27, VAR=28, IN=29, ASSIGN=30, AND=31, OR=32, - NOT=33, NULL=34, LEFT_PARENTHESIS=35, RIGHT_PARENTHESIS=36, ID=37, COMMENT=38, - MULTILINE_COMMENT=39, ATTRIBUTE_ID=40, STRING=41, NUMBER=42, WHITESPACE=43, - NEWLINE=44; + NOT=33, NULL=34, RETURN=35, LEFT_PARENTHESIS=36, RIGHT_PARENTHESIS=37, + ID=38, COMMENT=39, MULTILINE_COMMENT=40, ATTRIBUTE_ID=41, STRING=42, NUMBER=43, + WHITESPACE=44, NEWLINE=45; public static string[] modeNames = { "DEFAULT_MODE" }; @@ -44,14 +44,14 @@ public const int "';'", "'<='", "'to'", "'>'", "'by'", "'=='", "'/'", "'>='", "'function'", "'foreach'", "'while'", "'if'", "'else'", "'do'", "'end'", "BOOLEAN", "'skip'", "'var'", "'in'", "'='", "'and'", "'or'", "'not'", "'null'", - "'('", "')'", "ID", "COMMENT", "MULTILINE_COMMENT", "ATTRIBUTE_ID", "STRING", - "NUMBER", "WHITESPACE", "NEWLINE" + "'return'", "'('", "')'", "ID", "COMMENT", "MULTILINE_COMMENT", "ATTRIBUTE_ID", + "STRING", "NUMBER", "WHITESPACE", "NEWLINE" }; public static readonly string[] ruleNames = { "T__17", "T__16", "T__15", "T__14", "T__13", "T__12", "T__11", "T__10", "T__9", "T__8", "T__7", "T__6", "T__5", "T__4", "T__3", "T__2", "T__1", "T__0", "FUNCTION", "FOREACH", "WHILE", "IF", "ELSE", "DO", "END", "BOOLEAN", - "SKIP", "VAR", "IN", "ASSIGN", "AND", "OR", "NOT", "NULL", "LEFT_PARENTHESIS", + "SKIP", "VAR", "IN", "ASSIGN", "AND", "OR", "NOT", "NULL", "RETURN", "LEFT_PARENTHESIS", "RIGHT_PARENTHESIS", "ID", "COMMENT", "MULTILINE_COMMENT", "ATTRIBUTE_ID", "LETTER", "STRING", "NUMBER", "INT", "FLOAT", "DIGIT", "WHITESPACE", "NEWLINE" }; @@ -74,131 +74,135 @@ public MetaCodeLexer(ICharStream input) public override string SerializedAtn { get { return _serializedATN; } } public static readonly string _serializedATN = - "\x3\xAF6F\x8320\x479D\xB75C\x4880\x1605\x191C\xAB37\x2.\x14A\b\x1\x4\x2"+ + "\x3\xAF6F\x8320\x479D\xB75C\x4880\x1605\x191C\xAB37\x2/\x153\b\x1\x4\x2"+ "\t\x2\x4\x3\t\x3\x4\x4\t\x4\x4\x5\t\x5\x4\x6\t\x6\x4\a\t\a\x4\b\t\b\x4"+ "\t\t\t\x4\n\t\n\x4\v\t\v\x4\f\t\f\x4\r\t\r\x4\xE\t\xE\x4\xF\t\xF\x4\x10"+ "\t\x10\x4\x11\t\x11\x4\x12\t\x12\x4\x13\t\x13\x4\x14\t\x14\x4\x15\t\x15"+ "\x4\x16\t\x16\x4\x17\t\x17\x4\x18\t\x18\x4\x19\t\x19\x4\x1A\t\x1A\x4\x1B"+ "\t\x1B\x4\x1C\t\x1C\x4\x1D\t\x1D\x4\x1E\t\x1E\x4\x1F\t\x1F\x4 \t \x4!"+ "\t!\x4\"\t\"\x4#\t#\x4$\t$\x4%\t%\x4&\t&\x4\'\t\'\x4(\t(\x4)\t)\x4*\t"+ - "*\x4+\t+\x4,\t,\x4-\t-\x4.\t.\x4/\t/\x4\x30\t\x30\x4\x31\t\x31\x3\x2\x3"+ - "\x2\x3\x3\x3\x3\x3\x4\x3\x4\x3\x5\x3\x5\x3\x6\x3\x6\x3\a\x3\a\x3\b\x3"+ - "\b\x3\t\x3\t\x3\n\x3\n\x3\v\x3\v\x3\v\x3\f\x3\f\x3\r\x3\r\x3\r\x3\xE\x3"+ - "\xE\x3\xE\x3\xF\x3\xF\x3\x10\x3\x10\x3\x10\x3\x11\x3\x11\x3\x11\x3\x12"+ - "\x3\x12\x3\x13\x3\x13\x3\x13\x3\x14\x3\x14\x3\x14\x3\x14\x3\x14\x3\x14"+ - "\x3\x14\x3\x14\x3\x14\x3\x15\x3\x15\x3\x15\x3\x15\x3\x15\x3\x15\x3\x15"+ - "\x3\x15\x3\x16\x3\x16\x3\x16\x3\x16\x3\x16\x3\x16\x3\x17\x3\x17\x3\x17"+ - "\x3\x18\x3\x18\x3\x18\x3\x18\x3\x18\x3\x19\x3\x19\x3\x19\x3\x1A\x3\x1A"+ - "\x3\x1A\x3\x1A\x3\x1B\x3\x1B\x3\x1B\x3\x1B\x3\x1B\x3\x1B\x3\x1B\x3\x1B"+ - "\x3\x1B\x5\x1B\xBD\n\x1B\x3\x1C\x3\x1C\x3\x1C\x3\x1C\x3\x1C\x3\x1D\x3"+ - "\x1D\x3\x1D\x3\x1D\x3\x1E\x3\x1E\x3\x1E\x3\x1F\x3\x1F\x3 \x3 \x3 \x3 "+ - "\x3!\x3!\x3!\x3\"\x3\"\x3\"\x3\"\x3#\x3#\x3#\x3#\x3#\x3$\x3$\x3%\x3%\x3"+ - "&\x3&\x5&\xE3\n&\x3&\x3&\a&\xE7\n&\f&\xE&\xEA\v&\x3\'\x3\'\x3\'\x3\'\a"+ - "\'\xF0\n\'\f\'\xE\'\xF3\v\'\x3\'\x3\'\x3\'\x3\'\x3(\x3(\x3(\x3(\a(\xFD"+ - "\n(\f(\xE(\x100\v(\x3(\x3(\x3(\x3(\x3(\x3)\x3)\x3)\x5)\x10A\n)\x3)\x3"+ - ")\a)\x10E\n)\f)\xE)\x111\v)\x3*\x3*\x3+\x3+\a+\x117\n+\f+\xE+\x11A\v+"+ - "\x3+\x3+\x3,\x3,\x5,\x120\n,\x3-\x6-\x123\n-\r-\xE-\x124\x3.\x6.\x128"+ - "\n.\r.\xE.\x129\x3.\x3.\a.\x12E\n.\f.\xE.\x131\v.\x3.\x3.\x6.\x135\n."+ - "\r.\xE.\x136\x5.\x139\n.\x3/\x3/\x3\x30\x6\x30\x13E\n\x30\r\x30\xE\x30"+ - "\x13F\x3\x30\x3\x30\x3\x31\x5\x31\x145\n\x31\x3\x31\x3\x31\x3\x31\x3\x31"+ - "\x5\xF1\xFE\x118\x2\x2\x32\x3\x2\x3\x5\x2\x4\a\x2\x5\t\x2\x6\v\x2\a\r"+ - "\x2\b\xF\x2\t\x11\x2\n\x13\x2\v\x15\x2\f\x17\x2\r\x19\x2\xE\x1B\x2\xF"+ - "\x1D\x2\x10\x1F\x2\x11!\x2\x12#\x2\x13%\x2\x14\'\x2\x15)\x2\x16+\x2\x17"+ - "-\x2\x18/\x2\x19\x31\x2\x1A\x33\x2\x1B\x35\x2\x1C\x37\x2\x1D\x39\x2\x1E"+ - ";\x2\x1F=\x2 ?\x2!\x41\x2\"\x43\x2#\x45\x2$G\x2%I\x2&K\x2\'M\x2(O\x2)"+ - "Q\x2*S\x2\x2U\x2+W\x2,Y\x2\x2[\x2\x2]\x2\x2_\x2-\x61\x2.\x3\x2\a\x4\x2"+ - "\x32;\x61\x61\x5\x2//\x32;\x61\x61\x4\x2\x43\\\x63|\x3\x2\x32;\x4\x2\v"+ - "\v\"\"\x157\x2\x3\x3\x2\x2\x2\x2\x5\x3\x2\x2\x2\x2\a\x3\x2\x2\x2\x2\t"+ - "\x3\x2\x2\x2\x2\v\x3\x2\x2\x2\x2\r\x3\x2\x2\x2\x2\xF\x3\x2\x2\x2\x2\x11"+ - "\x3\x2\x2\x2\x2\x13\x3\x2\x2\x2\x2\x15\x3\x2\x2\x2\x2\x17\x3\x2\x2\x2"+ - "\x2\x19\x3\x2\x2\x2\x2\x1B\x3\x2\x2\x2\x2\x1D\x3\x2\x2\x2\x2\x1F\x3\x2"+ - "\x2\x2\x2!\x3\x2\x2\x2\x2#\x3\x2\x2\x2\x2%\x3\x2\x2\x2\x2\'\x3\x2\x2\x2"+ - "\x2)\x3\x2\x2\x2\x2+\x3\x2\x2\x2\x2-\x3\x2\x2\x2\x2/\x3\x2\x2\x2\x2\x31"+ - "\x3\x2\x2\x2\x2\x33\x3\x2\x2\x2\x2\x35\x3\x2\x2\x2\x2\x37\x3\x2\x2\x2"+ - "\x2\x39\x3\x2\x2\x2\x2;\x3\x2\x2\x2\x2=\x3\x2\x2\x2\x2?\x3\x2\x2\x2\x2"+ - "\x41\x3\x2\x2\x2\x2\x43\x3\x2\x2\x2\x2\x45\x3\x2\x2\x2\x2G\x3\x2\x2\x2"+ - "\x2I\x3\x2\x2\x2\x2K\x3\x2\x2\x2\x2M\x3\x2\x2\x2\x2O\x3\x2\x2\x2\x2Q\x3"+ - "\x2\x2\x2\x2U\x3\x2\x2\x2\x2W\x3\x2\x2\x2\x2_\x3\x2\x2\x2\x2\x61\x3\x2"+ - "\x2\x2\x3\x63\x3\x2\x2\x2\x5\x65\x3\x2\x2\x2\ag\x3\x2\x2\x2\ti\x3\x2\x2"+ - "\x2\vk\x3\x2\x2\x2\rm\x3\x2\x2\x2\xFo\x3\x2\x2\x2\x11q\x3\x2\x2\x2\x13"+ - "s\x3\x2\x2\x2\x15u\x3\x2\x2\x2\x17x\x3\x2\x2\x2\x19z\x3\x2\x2\x2\x1B}"+ - "\x3\x2\x2\x2\x1D\x80\x3\x2\x2\x2\x1F\x82\x3\x2\x2\x2!\x85\x3\x2\x2\x2"+ - "#\x88\x3\x2\x2\x2%\x8A\x3\x2\x2\x2\'\x8D\x3\x2\x2\x2)\x96\x3\x2\x2\x2"+ - "+\x9E\x3\x2\x2\x2-\xA4\x3\x2\x2\x2/\xA7\x3\x2\x2\x2\x31\xAC\x3\x2\x2\x2"+ - "\x33\xAF\x3\x2\x2\x2\x35\xBC\x3\x2\x2\x2\x37\xBE\x3\x2\x2\x2\x39\xC3\x3"+ - "\x2\x2\x2;\xC7\x3\x2\x2\x2=\xCA\x3\x2\x2\x2?\xCC\x3\x2\x2\x2\x41\xD0\x3"+ - "\x2\x2\x2\x43\xD3\x3\x2\x2\x2\x45\xD7\x3\x2\x2\x2G\xDC\x3\x2\x2\x2I\xDE"+ - "\x3\x2\x2\x2K\xE2\x3\x2\x2\x2M\xEB\x3\x2\x2\x2O\xF8\x3\x2\x2\x2Q\x106"+ - "\x3\x2\x2\x2S\x112\x3\x2\x2\x2U\x114\x3\x2\x2\x2W\x11F\x3\x2\x2\x2Y\x122"+ - "\x3\x2\x2\x2[\x138\x3\x2\x2\x2]\x13A\x3\x2\x2\x2_\x13D\x3\x2\x2\x2\x61"+ - "\x144\x3\x2\x2\x2\x63\x64\a_\x2\x2\x64\x4\x3\x2\x2\x2\x65\x66\a\x30\x2"+ - "\x2\x66\x6\x3\x2\x2\x2gh\a.\x2\x2h\b\x3\x2\x2\x2ij\a-\x2\x2j\n\x3\x2\x2"+ - "\x2kl\a,\x2\x2l\f\x3\x2\x2\x2mn\a/\x2\x2n\xE\x3\x2\x2\x2op\a]\x2\x2p\x10"+ - "\x3\x2\x2\x2qr\a<\x2\x2r\x12\x3\x2\x2\x2st\a>\x2\x2t\x14\x3\x2\x2\x2u"+ - "v\a#\x2\x2vw\a?\x2\x2w\x16\x3\x2\x2\x2xy\a=\x2\x2y\x18\x3\x2\x2\x2z{\a"+ - ">\x2\x2{|\a?\x2\x2|\x1A\x3\x2\x2\x2}~\av\x2\x2~\x7F\aq\x2\x2\x7F\x1C\x3"+ - "\x2\x2\x2\x80\x81\a@\x2\x2\x81\x1E\x3\x2\x2\x2\x82\x83\a\x64\x2\x2\x83"+ - "\x84\a{\x2\x2\x84 \x3\x2\x2\x2\x85\x86\a?\x2\x2\x86\x87\a?\x2\x2\x87\""+ - "\x3\x2\x2\x2\x88\x89\a\x31\x2\x2\x89$\x3\x2\x2\x2\x8A\x8B\a@\x2\x2\x8B"+ - "\x8C\a?\x2\x2\x8C&\x3\x2\x2\x2\x8D\x8E\ah\x2\x2\x8E\x8F\aw\x2\x2\x8F\x90"+ - "\ap\x2\x2\x90\x91\a\x65\x2\x2\x91\x92\av\x2\x2\x92\x93\ak\x2\x2\x93\x94"+ - "\aq\x2\x2\x94\x95\ap\x2\x2\x95(\x3\x2\x2\x2\x96\x97\ah\x2\x2\x97\x98\a"+ - "q\x2\x2\x98\x99\at\x2\x2\x99\x9A\ag\x2\x2\x9A\x9B\a\x63\x2\x2\x9B\x9C"+ - "\a\x65\x2\x2\x9C\x9D\aj\x2\x2\x9D*\x3\x2\x2\x2\x9E\x9F\ay\x2\x2\x9F\xA0"+ - "\aj\x2\x2\xA0\xA1\ak\x2\x2\xA1\xA2\an\x2\x2\xA2\xA3\ag\x2\x2\xA3,\x3\x2"+ - "\x2\x2\xA4\xA5\ak\x2\x2\xA5\xA6\ah\x2\x2\xA6.\x3\x2\x2\x2\xA7\xA8\ag\x2"+ - "\x2\xA8\xA9\an\x2\x2\xA9\xAA\au\x2\x2\xAA\xAB\ag\x2\x2\xAB\x30\x3\x2\x2"+ - "\x2\xAC\xAD\a\x66\x2\x2\xAD\xAE\aq\x2\x2\xAE\x32\x3\x2\x2\x2\xAF\xB0\a"+ - "g\x2\x2\xB0\xB1\ap\x2\x2\xB1\xB2\a\x66\x2\x2\xB2\x34\x3\x2\x2\x2\xB3\xB4"+ - "\ah\x2\x2\xB4\xB5\a\x63\x2\x2\xB5\xB6\an\x2\x2\xB6\xB7\au\x2\x2\xB7\xBD"+ - "\ag\x2\x2\xB8\xB9\av\x2\x2\xB9\xBA\at\x2\x2\xBA\xBB\aw\x2\x2\xBB\xBD\a"+ - "g\x2\x2\xBC\xB3\x3\x2\x2\x2\xBC\xB8\x3\x2\x2\x2\xBD\x36\x3\x2\x2\x2\xBE"+ - "\xBF\au\x2\x2\xBF\xC0\am\x2\x2\xC0\xC1\ak\x2\x2\xC1\xC2\ar\x2\x2\xC2\x38"+ - "\x3\x2\x2\x2\xC3\xC4\ax\x2\x2\xC4\xC5\a\x63\x2\x2\xC5\xC6\at\x2\x2\xC6"+ - ":\x3\x2\x2\x2\xC7\xC8\ak\x2\x2\xC8\xC9\ap\x2\x2\xC9<\x3\x2\x2\x2\xCA\xCB"+ - "\a?\x2\x2\xCB>\x3\x2\x2\x2\xCC\xCD\a\x63\x2\x2\xCD\xCE\ap\x2\x2\xCE\xCF"+ - "\a\x66\x2\x2\xCF@\x3\x2\x2\x2\xD0\xD1\aq\x2\x2\xD1\xD2\at\x2\x2\xD2\x42"+ - "\x3\x2\x2\x2\xD3\xD4\ap\x2\x2\xD4\xD5\aq\x2\x2\xD5\xD6\av\x2\x2\xD6\x44"+ - "\x3\x2\x2\x2\xD7\xD8\ap\x2\x2\xD8\xD9\aw\x2\x2\xD9\xDA\an\x2\x2\xDA\xDB"+ - "\an\x2\x2\xDB\x46\x3\x2\x2\x2\xDC\xDD\a*\x2\x2\xDDH\x3\x2\x2\x2\xDE\xDF"+ - "\a+\x2\x2\xDFJ\x3\x2\x2\x2\xE0\xE3\x5S*\x2\xE1\xE3\a\x61\x2\x2\xE2\xE0"+ - "\x3\x2\x2\x2\xE2\xE1\x3\x2\x2\x2\xE3\xE8\x3\x2\x2\x2\xE4\xE7\x5S*\x2\xE5"+ - "\xE7\t\x2\x2\x2\xE6\xE4\x3\x2\x2\x2\xE6\xE5\x3\x2\x2\x2\xE7\xEA\x3\x2"+ - "\x2\x2\xE8\xE6\x3\x2\x2\x2\xE8\xE9\x3\x2\x2\x2\xE9L\x3\x2\x2\x2\xEA\xE8"+ - "\x3\x2\x2\x2\xEB\xEC\a\x31\x2\x2\xEC\xED\a\x31\x2\x2\xED\xF1\x3\x2\x2"+ - "\x2\xEE\xF0\v\x2\x2\x2\xEF\xEE\x3\x2\x2\x2\xF0\xF3\x3\x2\x2\x2\xF1\xF2"+ - "\x3\x2\x2\x2\xF1\xEF\x3\x2\x2\x2\xF2\xF4\x3\x2\x2\x2\xF3\xF1\x3\x2\x2"+ - "\x2\xF4\xF5\x5\x61\x31\x2\xF5\xF6\x3\x2\x2\x2\xF6\xF7\b\'\x2\x2\xF7N\x3"+ - "\x2\x2\x2\xF8\xF9\a\x31\x2\x2\xF9\xFA\a,\x2\x2\xFA\xFE\x3\x2\x2\x2\xFB"+ - "\xFD\v\x2\x2\x2\xFC\xFB\x3\x2\x2\x2\xFD\x100\x3\x2\x2\x2\xFE\xFF\x3\x2"+ - "\x2\x2\xFE\xFC\x3\x2\x2\x2\xFF\x101\x3\x2\x2\x2\x100\xFE\x3\x2\x2\x2\x101"+ - "\x102\a,\x2\x2\x102\x103\a\x31\x2\x2\x103\x104\x3\x2\x2\x2\x104\x105\b"+ - "(\x2\x2\x105P\x3\x2\x2\x2\x106\x109\a\x42\x2\x2\x107\x10A\x5S*\x2\x108"+ - "\x10A\a\x61\x2\x2\x109\x107\x3\x2\x2\x2\x109\x108\x3\x2\x2\x2\x10A\x10F"+ - "\x3\x2\x2\x2\x10B\x10E\x5S*\x2\x10C\x10E\t\x3\x2\x2\x10D\x10B\x3\x2\x2"+ - "\x2\x10D\x10C\x3\x2\x2\x2\x10E\x111\x3\x2\x2\x2\x10F\x10D\x3\x2\x2\x2"+ - "\x10F\x110\x3\x2\x2\x2\x110R\x3\x2\x2\x2\x111\x10F\x3\x2\x2\x2\x112\x113"+ - "\t\x4\x2\x2\x113T\x3\x2\x2\x2\x114\x118\a$\x2\x2\x115\x117\v\x2\x2\x2"+ - "\x116\x115\x3\x2\x2\x2\x117\x11A\x3\x2\x2\x2\x118\x119\x3\x2\x2\x2\x118"+ - "\x116\x3\x2\x2\x2\x119\x11B\x3\x2\x2\x2\x11A\x118\x3\x2\x2\x2\x11B\x11C"+ - "\a$\x2\x2\x11CV\x3\x2\x2\x2\x11D\x120\x5Y-\x2\x11E\x120\x5[.\x2\x11F\x11D"+ - "\x3\x2\x2\x2\x11F\x11E\x3\x2\x2\x2\x120X\x3\x2\x2\x2\x121\x123\x5]/\x2"+ - "\x122\x121\x3\x2\x2\x2\x123\x124\x3\x2\x2\x2\x124\x122\x3\x2\x2\x2\x124"+ - "\x125\x3\x2\x2\x2\x125Z\x3\x2\x2\x2\x126\x128\x5]/\x2\x127\x126\x3\x2"+ - "\x2\x2\x128\x129\x3\x2\x2\x2\x129\x127\x3\x2\x2\x2\x129\x12A\x3\x2\x2"+ - "\x2\x12A\x12B\x3\x2\x2\x2\x12B\x12F\a\x30\x2\x2\x12C\x12E\x5]/\x2\x12D"+ - "\x12C\x3\x2\x2\x2\x12E\x131\x3\x2\x2\x2\x12F\x12D\x3\x2\x2\x2\x12F\x130"+ - "\x3\x2\x2\x2\x130\x139\x3\x2\x2\x2\x131\x12F\x3\x2\x2\x2\x132\x134\a\x30"+ - "\x2\x2\x133\x135\x5]/\x2\x134\x133\x3\x2\x2\x2\x135\x136\x3\x2\x2\x2\x136"+ - "\x134\x3\x2\x2\x2\x136\x137\x3\x2\x2\x2\x137\x139\x3\x2\x2\x2\x138\x127"+ - "\x3\x2\x2\x2\x138\x132\x3\x2\x2\x2\x139\\\x3\x2\x2\x2\x13A\x13B\t\x5\x2"+ - "\x2\x13B^\x3\x2\x2\x2\x13C\x13E\t\x6\x2\x2\x13D\x13C\x3\x2\x2\x2\x13E"+ - "\x13F\x3\x2\x2\x2\x13F\x13D\x3\x2\x2\x2\x13F\x140\x3\x2\x2\x2\x140\x141"+ - "\x3\x2\x2\x2\x141\x142\b\x30\x2\x2\x142`\x3\x2\x2\x2\x143\x145\a\xF\x2"+ - "\x2\x144\x143\x3\x2\x2\x2\x144\x145\x3\x2\x2\x2\x145\x146\x3\x2\x2\x2"+ - "\x146\x147\a\f\x2\x2\x147\x148\x3\x2\x2\x2\x148\x149\b\x31\x2\x2\x149"+ - "\x62\x3\x2\x2\x2\x15\x2\xBC\xE2\xE6\xE8\xF1\xFE\x109\x10D\x10F\x118\x11F"+ - "\x124\x129\x12F\x136\x138\x13F\x144\x3\b\x2\x2"; + "*\x4+\t+\x4,\t,\x4-\t-\x4.\t.\x4/\t/\x4\x30\t\x30\x4\x31\t\x31\x4\x32"+ + "\t\x32\x3\x2\x3\x2\x3\x3\x3\x3\x3\x4\x3\x4\x3\x5\x3\x5\x3\x6\x3\x6\x3"+ + "\a\x3\a\x3\b\x3\b\x3\t\x3\t\x3\n\x3\n\x3\v\x3\v\x3\v\x3\f\x3\f\x3\r\x3"+ + "\r\x3\r\x3\xE\x3\xE\x3\xE\x3\xF\x3\xF\x3\x10\x3\x10\x3\x10\x3\x11\x3\x11"+ + "\x3\x11\x3\x12\x3\x12\x3\x13\x3\x13\x3\x13\x3\x14\x3\x14\x3\x14\x3\x14"+ + "\x3\x14\x3\x14\x3\x14\x3\x14\x3\x14\x3\x15\x3\x15\x3\x15\x3\x15\x3\x15"+ + "\x3\x15\x3\x15\x3\x15\x3\x16\x3\x16\x3\x16\x3\x16\x3\x16\x3\x16\x3\x17"+ + "\x3\x17\x3\x17\x3\x18\x3\x18\x3\x18\x3\x18\x3\x18\x3\x19\x3\x19\x3\x19"+ + "\x3\x1A\x3\x1A\x3\x1A\x3\x1A\x3\x1B\x3\x1B\x3\x1B\x3\x1B\x3\x1B\x3\x1B"+ + "\x3\x1B\x3\x1B\x3\x1B\x5\x1B\xBF\n\x1B\x3\x1C\x3\x1C\x3\x1C\x3\x1C\x3"+ + "\x1C\x3\x1D\x3\x1D\x3\x1D\x3\x1D\x3\x1E\x3\x1E\x3\x1E\x3\x1F\x3\x1F\x3"+ + " \x3 \x3 \x3 \x3!\x3!\x3!\x3\"\x3\"\x3\"\x3\"\x3#\x3#\x3#\x3#\x3#\x3$"+ + "\x3$\x3$\x3$\x3$\x3$\x3$\x3%\x3%\x3&\x3&\x3\'\x3\'\x5\'\xEC\n\'\x3\'\x3"+ + "\'\a\'\xF0\n\'\f\'\xE\'\xF3\v\'\x3(\x3(\x3(\x3(\a(\xF9\n(\f(\xE(\xFC\v"+ + "(\x3(\x3(\x3(\x3(\x3)\x3)\x3)\x3)\a)\x106\n)\f)\xE)\x109\v)\x3)\x3)\x3"+ + ")\x3)\x3)\x3*\x3*\x3*\x5*\x113\n*\x3*\x3*\a*\x117\n*\f*\xE*\x11A\v*\x3"+ + "+\x3+\x3,\x3,\a,\x120\n,\f,\xE,\x123\v,\x3,\x3,\x3-\x3-\x5-\x129\n-\x3"+ + ".\x6.\x12C\n.\r.\xE.\x12D\x3/\x6/\x131\n/\r/\xE/\x132\x3/\x3/\a/\x137"+ + "\n/\f/\xE/\x13A\v/\x3/\x3/\x6/\x13E\n/\r/\xE/\x13F\x5/\x142\n/\x3\x30"+ + "\x3\x30\x3\x31\x6\x31\x147\n\x31\r\x31\xE\x31\x148\x3\x31\x3\x31\x3\x32"+ + "\x5\x32\x14E\n\x32\x3\x32\x3\x32\x3\x32\x3\x32\x5\xFA\x107\x121\x2\x2"+ + "\x33\x3\x2\x3\x5\x2\x4\a\x2\x5\t\x2\x6\v\x2\a\r\x2\b\xF\x2\t\x11\x2\n"+ + "\x13\x2\v\x15\x2\f\x17\x2\r\x19\x2\xE\x1B\x2\xF\x1D\x2\x10\x1F\x2\x11"+ + "!\x2\x12#\x2\x13%\x2\x14\'\x2\x15)\x2\x16+\x2\x17-\x2\x18/\x2\x19\x31"+ + "\x2\x1A\x33\x2\x1B\x35\x2\x1C\x37\x2\x1D\x39\x2\x1E;\x2\x1F=\x2 ?\x2!"+ + "\x41\x2\"\x43\x2#\x45\x2$G\x2%I\x2&K\x2\'M\x2(O\x2)Q\x2*S\x2+U\x2\x2W"+ + "\x2,Y\x2-[\x2\x2]\x2\x2_\x2\x2\x61\x2.\x63\x2/\x3\x2\a\x4\x2\x32;\x61"+ + "\x61\x5\x2//\x32;\x61\x61\x4\x2\x43\\\x63|\x3\x2\x32;\x4\x2\v\v\"\"\x160"+ + "\x2\x3\x3\x2\x2\x2\x2\x5\x3\x2\x2\x2\x2\a\x3\x2\x2\x2\x2\t\x3\x2\x2\x2"+ + "\x2\v\x3\x2\x2\x2\x2\r\x3\x2\x2\x2\x2\xF\x3\x2\x2\x2\x2\x11\x3\x2\x2\x2"+ + "\x2\x13\x3\x2\x2\x2\x2\x15\x3\x2\x2\x2\x2\x17\x3\x2\x2\x2\x2\x19\x3\x2"+ + "\x2\x2\x2\x1B\x3\x2\x2\x2\x2\x1D\x3\x2\x2\x2\x2\x1F\x3\x2\x2\x2\x2!\x3"+ + "\x2\x2\x2\x2#\x3\x2\x2\x2\x2%\x3\x2\x2\x2\x2\'\x3\x2\x2\x2\x2)\x3\x2\x2"+ + "\x2\x2+\x3\x2\x2\x2\x2-\x3\x2\x2\x2\x2/\x3\x2\x2\x2\x2\x31\x3\x2\x2\x2"+ + "\x2\x33\x3\x2\x2\x2\x2\x35\x3\x2\x2\x2\x2\x37\x3\x2\x2\x2\x2\x39\x3\x2"+ + "\x2\x2\x2;\x3\x2\x2\x2\x2=\x3\x2\x2\x2\x2?\x3\x2\x2\x2\x2\x41\x3\x2\x2"+ + "\x2\x2\x43\x3\x2\x2\x2\x2\x45\x3\x2\x2\x2\x2G\x3\x2\x2\x2\x2I\x3\x2\x2"+ + "\x2\x2K\x3\x2\x2\x2\x2M\x3\x2\x2\x2\x2O\x3\x2\x2\x2\x2Q\x3\x2\x2\x2\x2"+ + "S\x3\x2\x2\x2\x2W\x3\x2\x2\x2\x2Y\x3\x2\x2\x2\x2\x61\x3\x2\x2\x2\x2\x63"+ + "\x3\x2\x2\x2\x3\x65\x3\x2\x2\x2\x5g\x3\x2\x2\x2\ai\x3\x2\x2\x2\tk\x3\x2"+ + "\x2\x2\vm\x3\x2\x2\x2\ro\x3\x2\x2\x2\xFq\x3\x2\x2\x2\x11s\x3\x2\x2\x2"+ + "\x13u\x3\x2\x2\x2\x15w\x3\x2\x2\x2\x17z\x3\x2\x2\x2\x19|\x3\x2\x2\x2\x1B"+ + "\x7F\x3\x2\x2\x2\x1D\x82\x3\x2\x2\x2\x1F\x84\x3\x2\x2\x2!\x87\x3\x2\x2"+ + "\x2#\x8A\x3\x2\x2\x2%\x8C\x3\x2\x2\x2\'\x8F\x3\x2\x2\x2)\x98\x3\x2\x2"+ + "\x2+\xA0\x3\x2\x2\x2-\xA6\x3\x2\x2\x2/\xA9\x3\x2\x2\x2\x31\xAE\x3\x2\x2"+ + "\x2\x33\xB1\x3\x2\x2\x2\x35\xBE\x3\x2\x2\x2\x37\xC0\x3\x2\x2\x2\x39\xC5"+ + "\x3\x2\x2\x2;\xC9\x3\x2\x2\x2=\xCC\x3\x2\x2\x2?\xCE\x3\x2\x2\x2\x41\xD2"+ + "\x3\x2\x2\x2\x43\xD5\x3\x2\x2\x2\x45\xD9\x3\x2\x2\x2G\xDE\x3\x2\x2\x2"+ + "I\xE5\x3\x2\x2\x2K\xE7\x3\x2\x2\x2M\xEB\x3\x2\x2\x2O\xF4\x3\x2\x2\x2Q"+ + "\x101\x3\x2\x2\x2S\x10F\x3\x2\x2\x2U\x11B\x3\x2\x2\x2W\x11D\x3\x2\x2\x2"+ + "Y\x128\x3\x2\x2\x2[\x12B\x3\x2\x2\x2]\x141\x3\x2\x2\x2_\x143\x3\x2\x2"+ + "\x2\x61\x146\x3\x2\x2\x2\x63\x14D\x3\x2\x2\x2\x65\x66\a_\x2\x2\x66\x4"+ + "\x3\x2\x2\x2gh\a\x30\x2\x2h\x6\x3\x2\x2\x2ij\a.\x2\x2j\b\x3\x2\x2\x2k"+ + "l\a-\x2\x2l\n\x3\x2\x2\x2mn\a,\x2\x2n\f\x3\x2\x2\x2op\a/\x2\x2p\xE\x3"+ + "\x2\x2\x2qr\a]\x2\x2r\x10\x3\x2\x2\x2st\a<\x2\x2t\x12\x3\x2\x2\x2uv\a"+ + ">\x2\x2v\x14\x3\x2\x2\x2wx\a#\x2\x2xy\a?\x2\x2y\x16\x3\x2\x2\x2z{\a=\x2"+ + "\x2{\x18\x3\x2\x2\x2|}\a>\x2\x2}~\a?\x2\x2~\x1A\x3\x2\x2\x2\x7F\x80\a"+ + "v\x2\x2\x80\x81\aq\x2\x2\x81\x1C\x3\x2\x2\x2\x82\x83\a@\x2\x2\x83\x1E"+ + "\x3\x2\x2\x2\x84\x85\a\x64\x2\x2\x85\x86\a{\x2\x2\x86 \x3\x2\x2\x2\x87"+ + "\x88\a?\x2\x2\x88\x89\a?\x2\x2\x89\"\x3\x2\x2\x2\x8A\x8B\a\x31\x2\x2\x8B"+ + "$\x3\x2\x2\x2\x8C\x8D\a@\x2\x2\x8D\x8E\a?\x2\x2\x8E&\x3\x2\x2\x2\x8F\x90"+ + "\ah\x2\x2\x90\x91\aw\x2\x2\x91\x92\ap\x2\x2\x92\x93\a\x65\x2\x2\x93\x94"+ + "\av\x2\x2\x94\x95\ak\x2\x2\x95\x96\aq\x2\x2\x96\x97\ap\x2\x2\x97(\x3\x2"+ + "\x2\x2\x98\x99\ah\x2\x2\x99\x9A\aq\x2\x2\x9A\x9B\at\x2\x2\x9B\x9C\ag\x2"+ + "\x2\x9C\x9D\a\x63\x2\x2\x9D\x9E\a\x65\x2\x2\x9E\x9F\aj\x2\x2\x9F*\x3\x2"+ + "\x2\x2\xA0\xA1\ay\x2\x2\xA1\xA2\aj\x2\x2\xA2\xA3\ak\x2\x2\xA3\xA4\an\x2"+ + "\x2\xA4\xA5\ag\x2\x2\xA5,\x3\x2\x2\x2\xA6\xA7\ak\x2\x2\xA7\xA8\ah\x2\x2"+ + "\xA8.\x3\x2\x2\x2\xA9\xAA\ag\x2\x2\xAA\xAB\an\x2\x2\xAB\xAC\au\x2\x2\xAC"+ + "\xAD\ag\x2\x2\xAD\x30\x3\x2\x2\x2\xAE\xAF\a\x66\x2\x2\xAF\xB0\aq\x2\x2"+ + "\xB0\x32\x3\x2\x2\x2\xB1\xB2\ag\x2\x2\xB2\xB3\ap\x2\x2\xB3\xB4\a\x66\x2"+ + "\x2\xB4\x34\x3\x2\x2\x2\xB5\xB6\ah\x2\x2\xB6\xB7\a\x63\x2\x2\xB7\xB8\a"+ + "n\x2\x2\xB8\xB9\au\x2\x2\xB9\xBF\ag\x2\x2\xBA\xBB\av\x2\x2\xBB\xBC\at"+ + "\x2\x2\xBC\xBD\aw\x2\x2\xBD\xBF\ag\x2\x2\xBE\xB5\x3\x2\x2\x2\xBE\xBA\x3"+ + "\x2\x2\x2\xBF\x36\x3\x2\x2\x2\xC0\xC1\au\x2\x2\xC1\xC2\am\x2\x2\xC2\xC3"+ + "\ak\x2\x2\xC3\xC4\ar\x2\x2\xC4\x38\x3\x2\x2\x2\xC5\xC6\ax\x2\x2\xC6\xC7"+ + "\a\x63\x2\x2\xC7\xC8\at\x2\x2\xC8:\x3\x2\x2\x2\xC9\xCA\ak\x2\x2\xCA\xCB"+ + "\ap\x2\x2\xCB<\x3\x2\x2\x2\xCC\xCD\a?\x2\x2\xCD>\x3\x2\x2\x2\xCE\xCF\a"+ + "\x63\x2\x2\xCF\xD0\ap\x2\x2\xD0\xD1\a\x66\x2\x2\xD1@\x3\x2\x2\x2\xD2\xD3"+ + "\aq\x2\x2\xD3\xD4\at\x2\x2\xD4\x42\x3\x2\x2\x2\xD5\xD6\ap\x2\x2\xD6\xD7"+ + "\aq\x2\x2\xD7\xD8\av\x2\x2\xD8\x44\x3\x2\x2\x2\xD9\xDA\ap\x2\x2\xDA\xDB"+ + "\aw\x2\x2\xDB\xDC\an\x2\x2\xDC\xDD\an\x2\x2\xDD\x46\x3\x2\x2\x2\xDE\xDF"+ + "\at\x2\x2\xDF\xE0\ag\x2\x2\xE0\xE1\av\x2\x2\xE1\xE2\aw\x2\x2\xE2\xE3\a"+ + "t\x2\x2\xE3\xE4\ap\x2\x2\xE4H\x3\x2\x2\x2\xE5\xE6\a*\x2\x2\xE6J\x3\x2"+ + "\x2\x2\xE7\xE8\a+\x2\x2\xE8L\x3\x2\x2\x2\xE9\xEC\x5U+\x2\xEA\xEC\a\x61"+ + "\x2\x2\xEB\xE9\x3\x2\x2\x2\xEB\xEA\x3\x2\x2\x2\xEC\xF1\x3\x2\x2\x2\xED"+ + "\xF0\x5U+\x2\xEE\xF0\t\x2\x2\x2\xEF\xED\x3\x2\x2\x2\xEF\xEE\x3\x2\x2\x2"+ + "\xF0\xF3\x3\x2\x2\x2\xF1\xEF\x3\x2\x2\x2\xF1\xF2\x3\x2\x2\x2\xF2N\x3\x2"+ + "\x2\x2\xF3\xF1\x3\x2\x2\x2\xF4\xF5\a\x31\x2\x2\xF5\xF6\a\x31\x2\x2\xF6"+ + "\xFA\x3\x2\x2\x2\xF7\xF9\v\x2\x2\x2\xF8\xF7\x3\x2\x2\x2\xF9\xFC\x3\x2"+ + "\x2\x2\xFA\xFB\x3\x2\x2\x2\xFA\xF8\x3\x2\x2\x2\xFB\xFD\x3\x2\x2\x2\xFC"+ + "\xFA\x3\x2\x2\x2\xFD\xFE\x5\x63\x32\x2\xFE\xFF\x3\x2\x2\x2\xFF\x100\b"+ + "(\x2\x2\x100P\x3\x2\x2\x2\x101\x102\a\x31\x2\x2\x102\x103\a,\x2\x2\x103"+ + "\x107\x3\x2\x2\x2\x104\x106\v\x2\x2\x2\x105\x104\x3\x2\x2\x2\x106\x109"+ + "\x3\x2\x2\x2\x107\x108\x3\x2\x2\x2\x107\x105\x3\x2\x2\x2\x108\x10A\x3"+ + "\x2\x2\x2\x109\x107\x3\x2\x2\x2\x10A\x10B\a,\x2\x2\x10B\x10C\a\x31\x2"+ + "\x2\x10C\x10D\x3\x2\x2\x2\x10D\x10E\b)\x2\x2\x10ER\x3\x2\x2\x2\x10F\x112"+ + "\a\x42\x2\x2\x110\x113\x5U+\x2\x111\x113\a\x61\x2\x2\x112\x110\x3\x2\x2"+ + "\x2\x112\x111\x3\x2\x2\x2\x113\x118\x3\x2\x2\x2\x114\x117\x5U+\x2\x115"+ + "\x117\t\x3\x2\x2\x116\x114\x3\x2\x2\x2\x116\x115\x3\x2\x2\x2\x117\x11A"+ + "\x3\x2\x2\x2\x118\x116\x3\x2\x2\x2\x118\x119\x3\x2\x2\x2\x119T\x3\x2\x2"+ + "\x2\x11A\x118\x3\x2\x2\x2\x11B\x11C\t\x4\x2\x2\x11CV\x3\x2\x2\x2\x11D"+ + "\x121\a$\x2\x2\x11E\x120\v\x2\x2\x2\x11F\x11E\x3\x2\x2\x2\x120\x123\x3"+ + "\x2\x2\x2\x121\x122\x3\x2\x2\x2\x121\x11F\x3\x2\x2\x2\x122\x124\x3\x2"+ + "\x2\x2\x123\x121\x3\x2\x2\x2\x124\x125\a$\x2\x2\x125X\x3\x2\x2\x2\x126"+ + "\x129\x5[.\x2\x127\x129\x5]/\x2\x128\x126\x3\x2\x2\x2\x128\x127\x3\x2"+ + "\x2\x2\x129Z\x3\x2\x2\x2\x12A\x12C\x5_\x30\x2\x12B\x12A\x3\x2\x2\x2\x12C"+ + "\x12D\x3\x2\x2\x2\x12D\x12B\x3\x2\x2\x2\x12D\x12E\x3\x2\x2\x2\x12E\\\x3"+ + "\x2\x2\x2\x12F\x131\x5_\x30\x2\x130\x12F\x3\x2\x2\x2\x131\x132\x3\x2\x2"+ + "\x2\x132\x130\x3\x2\x2\x2\x132\x133\x3\x2\x2\x2\x133\x134\x3\x2\x2\x2"+ + "\x134\x138\a\x30\x2\x2\x135\x137\x5_\x30\x2\x136\x135\x3\x2\x2\x2\x137"+ + "\x13A\x3\x2\x2\x2\x138\x136\x3\x2\x2\x2\x138\x139\x3\x2\x2\x2\x139\x142"+ + "\x3\x2\x2\x2\x13A\x138\x3\x2\x2\x2\x13B\x13D\a\x30\x2\x2\x13C\x13E\x5"+ + "_\x30\x2\x13D\x13C\x3\x2\x2\x2\x13E\x13F\x3\x2\x2\x2\x13F\x13D\x3\x2\x2"+ + "\x2\x13F\x140\x3\x2\x2\x2\x140\x142\x3\x2\x2\x2\x141\x130\x3\x2\x2\x2"+ + "\x141\x13B\x3\x2\x2\x2\x142^\x3\x2\x2\x2\x143\x144\t\x5\x2\x2\x144`\x3"+ + "\x2\x2\x2\x145\x147\t\x6\x2\x2\x146\x145\x3\x2\x2\x2\x147\x148\x3\x2\x2"+ + "\x2\x148\x146\x3\x2\x2\x2\x148\x149\x3\x2\x2\x2\x149\x14A\x3\x2\x2\x2"+ + "\x14A\x14B\b\x31\x2\x2\x14B\x62\x3\x2\x2\x2\x14C\x14E\a\xF\x2\x2\x14D"+ + "\x14C\x3\x2\x2\x2\x14D\x14E\x3\x2\x2\x2\x14E\x14F\x3\x2\x2\x2\x14F\x150"+ + "\a\f\x2\x2\x150\x151\x3\x2\x2\x2\x151\x152\b\x32\x2\x2\x152\x64\x3\x2"+ + "\x2\x2\x15\x2\xBE\xEB\xEF\xF1\xFA\x107\x112\x116\x118\x121\x128\x12D\x132"+ + "\x138\x13F\x141\x148\x14D\x3\b\x2\x2"; public static readonly ATN _ATN = new ATNDeserializer().Deserialize(_serializedATN.ToCharArray()); } diff --git a/project/MetaCode/MetaCode.Compiler/obj/Debug/MetaCodeLexer.tokens b/project/MetaCode/MetaCode.Compiler/obj/Debug/MetaCodeLexer.tokens index 7b17149..e763b7a 100644 --- a/project/MetaCode/MetaCode.Compiler/obj/Debug/MetaCodeLexer.tokens +++ b/project/MetaCode/MetaCode.Compiler/obj/Debug/MetaCodeLexer.tokens @@ -2,12 +2,12 @@ FUNCTION=19 WHILE=21 NULL=34 ELSE=23 -NUMBER=42 -ATTRIBUTE_ID=40 -WHITESPACE=43 +NUMBER=43 +ATTRIBUTE_ID=41 +WHITESPACE=44 DO=24 NOT=33 -ID=37 +ID=38 AND=31 T__9=9 T__8=10 @@ -17,31 +17,32 @@ T__5=13 IF=22 SKIP=27 T__4=14 -MULTILINE_COMMENT=39 +MULTILINE_COMMENT=40 BOOLEAN=26 T__16=2 IN=29 T__15=3 -NEWLINE=44 -RIGHT_PARENTHESIS=36 +NEWLINE=45 +RIGHT_PARENTHESIS=37 T__17=1 T__12=6 T__11=7 T__14=4 -LEFT_PARENTHESIS=35 +LEFT_PARENTHESIS=36 T__13=5 T__1=17 T__0=18 OR=32 T__10=8 T__3=15 +RETURN=35 ASSIGN=30 T__2=16 VAR=28 FOREACH=20 -COMMENT=38 +COMMENT=39 END=25 -STRING=41 +STRING=42 'foreach'=20 'end'=25 '>='=18 @@ -50,9 +51,10 @@ STRING=41 'null'=34 '>'=14 ';'=11 +'return'=35 '='=30 '+'=4 -')'=36 +')'=37 '.'=2 'function'=19 'do'=24 @@ -66,7 +68,7 @@ STRING=41 '!='=10 '<'=9 'if'=22 -'('=35 +'('=36 'not'=33 ':'=8 'or'=32 diff --git a/project/MetaCode/MetaCode.Compiler/obj/Debug/MetaCodeListener.cs b/project/MetaCode/MetaCode.Compiler/obj/Debug/MetaCodeListener.cs index 4220941..9fc20d9 100644 --- a/project/MetaCode/MetaCode.Compiler/obj/Debug/MetaCodeListener.cs +++ b/project/MetaCode/MetaCode.Compiler/obj/Debug/MetaCodeListener.cs @@ -8,7 +8,7 @@ // //------------------------------------------------------------------------------ -// Generated from E:\Development\Projects\C#\MetaCode\project\MetaCode\MetaCode.Compiler\Grammar\MetaCode.g4 by ANTLR 4.2-SNAPSHOT +// Generated from E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler\Grammar\MetaCode.g4 by ANTLR 4.2-SNAPSHOT // Unreachable code detected #pragma warning disable 0162 @@ -51,6 +51,17 @@ public interface IMetaCodeListener : IParseTreeListener { /// The parse tree. void ExitFormalParameter([NotNull] MetaCodeParser.FormalParameterContext context); + /// + /// Enter a parse tree produced by . + /// + /// The parse tree. + void EnterReturnStatement([NotNull] MetaCodeParser.ReturnStatementContext context); + /// + /// Exit a parse tree produced by . + /// + /// The parse tree. + void ExitReturnStatement([NotNull] MetaCodeParser.ReturnStatementContext context); + /// /// Enter a parse tree produced by . /// @@ -194,6 +205,17 @@ public interface IMetaCodeListener : IParseTreeListener { /// The parse tree. void ExitBooleanConstant([NotNull] MetaCodeParser.BooleanConstantContext context); + /// + /// Enter a parse tree produced by . + /// + /// The parse tree. + void EnterMemberTagExpression([NotNull] MetaCodeParser.MemberTagExpressionContext context); + /// + /// Exit a parse tree produced by . + /// + /// The parse tree. + void ExitMemberTagExpression([NotNull] MetaCodeParser.MemberTagExpressionContext context); + /// /// Enter a parse tree produced by . /// @@ -326,17 +348,6 @@ public interface IMetaCodeListener : IParseTreeListener { /// The parse tree. void ExitForeachStatement([NotNull] MetaCodeParser.ForeachStatementContext context); - /// - /// Enter a parse tree produced by . - /// - /// The parse tree. - void EnterIdentifier([NotNull] MetaCodeParser.IdentifierContext context); - /// - /// Exit a parse tree produced by . - /// - /// The parse tree. - void ExitIdentifier([NotNull] MetaCodeParser.IdentifierContext context); - /// /// Enter a parse tree produced by . /// diff --git a/project/MetaCode/MetaCode.Compiler/obj/Debug/MetaCodeParser.cs b/project/MetaCode/MetaCode.Compiler/obj/Debug/MetaCodeParser.cs index 9978c14..4b3195b 100644 --- a/project/MetaCode/MetaCode.Compiler/obj/Debug/MetaCodeParser.cs +++ b/project/MetaCode/MetaCode.Compiler/obj/Debug/MetaCodeParser.cs @@ -8,7 +8,7 @@ // //------------------------------------------------------------------------------ -// Generated from E:\Development\Projects\C#\MetaCode\project\MetaCode\MetaCode.Compiler\Grammar\MetaCode.g4 by ANTLR 4.2-SNAPSHOT +// Generated from E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler\Grammar\MetaCode.g4 by ANTLR 4.2-SNAPSHOT // Unreachable code detected #pragma warning disable 0162 @@ -33,35 +33,36 @@ public const int T__9=9, T__8=10, T__7=11, T__6=12, T__5=13, T__4=14, T__3=15, T__2=16, T__1=17, T__0=18, FUNCTION=19, FOREACH=20, WHILE=21, IF=22, ELSE=23, DO=24, END=25, BOOLEAN=26, SKIP=27, VAR=28, IN=29, ASSIGN=30, AND=31, OR=32, - NOT=33, NULL=34, LEFT_PARENTHESIS=35, RIGHT_PARENTHESIS=36, ID=37, COMMENT=38, - MULTILINE_COMMENT=39, ATTRIBUTE_ID=40, STRING=41, NUMBER=42, WHITESPACE=43, - NEWLINE=44; + NOT=33, NULL=34, RETURN=35, LEFT_PARENTHESIS=36, RIGHT_PARENTHESIS=37, + ID=38, COMMENT=39, MULTILINE_COMMENT=40, ATTRIBUTE_ID=41, STRING=42, NUMBER=43, + WHITESPACE=44, NEWLINE=45; public static readonly string[] tokenNames = { "", "']'", "'.'", "','", "'+'", "'*'", "'-'", "'['", "':'", "'<'", "'!='", "';'", "'<='", "'to'", "'>'", "'by'", "'=='", "'/'", "'>='", "'function'", "'foreach'", "'while'", "'if'", "'else'", "'do'", "'end'", "BOOLEAN", "'skip'", "'var'", "'in'", "'='", "'and'", "'or'", "'not'", "'null'", - "'('", "')'", "ID", "COMMENT", "MULTILINE_COMMENT", "ATTRIBUTE_ID", "STRING", - "NUMBER", "WHITESPACE", "NEWLINE" + "'return'", "'('", "')'", "ID", "COMMENT", "MULTILINE_COMMENT", "ATTRIBUTE_ID", + "STRING", "NUMBER", "WHITESPACE", "NEWLINE" }; public const int RULE_init = 0, RULE_statements = 1, RULE_statement = 2, RULE_variableDeclaration = 3, RULE_expression = 4, RULE_functionCallExpression = 5, RULE_memberExpression = 6, - RULE_primaryExpression = 7, RULE_functionExpression = 8, RULE_foreachStatement = 9, - RULE_whileStatement = 10, RULE_blockStatement = 11, RULE_skipStatement = 12, - RULE_assignmentExpression = 13, RULE_ifStatement = 14, RULE_elseIfStatement = 15, - RULE_formalParameterList = 16, RULE_formalParameter = 17, RULE_actualParameterList = 18, - RULE_typeName = 19, RULE_constant = 20, RULE_identifier = 21, RULE_numberConstant = 22, - RULE_stringConstant = 23, RULE_booleanConstant = 24, RULE_arrayConstant = 25, - RULE_intervalConstant = 26, RULE_attributes = 27, RULE_attribute = 28; + RULE_memberTagExpression = 7, RULE_primaryExpression = 8, RULE_functionExpression = 9, + RULE_foreachStatement = 10, RULE_whileStatement = 11, RULE_blockStatement = 12, + RULE_skipStatement = 13, RULE_returnStatement = 14, RULE_assignmentExpression = 15, + RULE_ifStatement = 16, RULE_elseIfStatement = 17, RULE_formalParameterList = 18, + RULE_formalParameter = 19, RULE_actualParameterList = 20, RULE_typeName = 21, + RULE_constant = 22, RULE_numberConstant = 23, RULE_stringConstant = 24, + RULE_booleanConstant = 25, RULE_arrayConstant = 26, RULE_intervalConstant = 27, + RULE_attributes = 28, RULE_attribute = 29; public static readonly string[] ruleNames = { "init", "statements", "statement", "variableDeclaration", "expression", - "functionCallExpression", "memberExpression", "primaryExpression", "functionExpression", - "foreachStatement", "whileStatement", "blockStatement", "skipStatement", - "assignmentExpression", "ifStatement", "elseIfStatement", "formalParameterList", - "formalParameter", "actualParameterList", "typeName", "constant", "identifier", - "numberConstant", "stringConstant", "booleanConstant", "arrayConstant", - "intervalConstant", "attributes", "attribute" + "functionCallExpression", "memberExpression", "memberTagExpression", "primaryExpression", + "functionExpression", "foreachStatement", "whileStatement", "blockStatement", + "skipStatement", "returnStatement", "assignmentExpression", "ifStatement", + "elseIfStatement", "formalParameterList", "formalParameter", "actualParameterList", + "typeName", "constant", "numberConstant", "stringConstant", "booleanConstant", + "arrayConstant", "intervalConstant", "attributes", "attribute" }; public override string GrammarFileName { get { return "MetaCode.g4"; } } @@ -108,7 +109,7 @@ public InitContext init() { try { EnterOuterAlt(_localctx, 1); { - State = 58; statements(); + State = 60; statements(); } } catch (RecognitionException re) { @@ -157,20 +158,20 @@ public StatementsContext statements() { try { EnterOuterAlt(_localctx, 1); { - State = 63; + State = 65; _errHandler.Sync(this); _la = _input.La(1); do { { { - State = 60; statement(); - State = 61; Match(11); + State = 62; statement(); + State = 63; Match(11); } } - State = 65; + State = 67; _errHandler.Sync(this); _la = _input.La(1); - } while ( (((_la) & ~0x3f) == 0 && ((1L << _la) & ((1L << 7) | (1L << FUNCTION) | (1L << FOREACH) | (1L << WHILE) | (1L << IF) | (1L << DO) | (1L << BOOLEAN) | (1L << SKIP) | (1L << VAR) | (1L << NOT) | (1L << LEFT_PARENTHESIS) | (1L << ID) | (1L << ATTRIBUTE_ID) | (1L << STRING) | (1L << NUMBER))) != 0) ); + } while ( (((_la) & ~0x3f) == 0 && ((1L << _la) & ((1L << 7) | (1L << FUNCTION) | (1L << FOREACH) | (1L << WHILE) | (1L << IF) | (1L << DO) | (1L << BOOLEAN) | (1L << SKIP) | (1L << VAR) | (1L << NOT) | (1L << RETURN) | (1L << LEFT_PARENTHESIS) | (1L << ID) | (1L << ATTRIBUTE_ID) | (1L << STRING) | (1L << NUMBER))) != 0) ); } } catch (RecognitionException re) { @@ -186,6 +187,7 @@ public StatementsContext statements() { public partial class StatementContext : ParserRuleContext { public ExpressionContext Expression; + public ReturnStatementContext Return; public AttributesContext Attributes; public VariableDeclarationContext VariableDeclaration; public IfStatementContext If; @@ -205,6 +207,9 @@ public IfStatementContext ifStatement() { public SkipStatementContext skipStatement() { return GetRuleContext(0); } + public ReturnStatementContext returnStatement() { + return GetRuleContext(0); + } public WhileStatementContext whileStatement() { return GetRuleContext(0); } @@ -243,102 +248,109 @@ public StatementContext statement() { EnterRule(_localctx, 4, RULE_statement); int _la; try { - State = 92; + State = 95; switch ( Interpreter.AdaptivePredict(_input,7,_ctx) ) { case 1: EnterOuterAlt(_localctx, 1); { - State = 67; _localctx.Expression = expression(0); + State = 69; _localctx.Expression = expression(0); } break; case 2: EnterOuterAlt(_localctx, 2); { - State = 69; - switch ( Interpreter.AdaptivePredict(_input,1,_ctx) ) { - case 1: - { - State = 68; _localctx.Attributes = attributes(); - } - break; - } - State = 71; _localctx.VariableDeclaration = variableDeclaration(); + State = 70; _localctx.Return = returnStatement(); } break; case 3: EnterOuterAlt(_localctx, 3); { - State = 73; - _la = _input.La(1); - if (_la==ATTRIBUTE_ID) { + State = 72; + switch ( Interpreter.AdaptivePredict(_input,1,_ctx) ) { + case 1: { - State = 72; _localctx.Attributes = attributes(); + State = 71; _localctx.Attributes = attributes(); } + break; } - - State = 75; _localctx.If = ifStatement(); + State = 74; _localctx.VariableDeclaration = variableDeclaration(); } break; case 4: EnterOuterAlt(_localctx, 4); { - State = 77; + State = 76; _la = _input.La(1); if (_la==ATTRIBUTE_ID) { { - State = 76; _localctx.Attributes = attributes(); + State = 75; _localctx.Attributes = attributes(); } } - State = 79; _localctx.Block = blockStatement(); + State = 78; _localctx.If = ifStatement(); } break; case 5: EnterOuterAlt(_localctx, 5); { - State = 81; + State = 80; _la = _input.La(1); if (_la==ATTRIBUTE_ID) { { - State = 80; _localctx.Attributes = attributes(); + State = 79; _localctx.Attributes = attributes(); } } - State = 83; _localctx.Foreach = foreachStatement(); + State = 82; _localctx.Block = blockStatement(); } break; case 6: EnterOuterAlt(_localctx, 6); { - State = 85; + State = 84; _la = _input.La(1); if (_la==ATTRIBUTE_ID) { { - State = 84; _localctx.Attributes = attributes(); + State = 83; _localctx.Attributes = attributes(); } } - State = 87; _localctx.While = whileStatement(); + State = 86; _localctx.Foreach = foreachStatement(); } break; case 7: EnterOuterAlt(_localctx, 7); { - State = 89; + State = 88; + _la = _input.La(1); + if (_la==ATTRIBUTE_ID) { + { + State = 87; _localctx.Attributes = attributes(); + } + } + + State = 90; _localctx.While = whileStatement(); + } + break; + + case 8: + EnterOuterAlt(_localctx, 8); + { + State = 92; _la = _input.La(1); if (_la==ATTRIBUTE_ID) { { - State = 88; _localctx.Attributes = attributes(); + State = 91; _localctx.Attributes = attributes(); } } - State = 91; _localctx.Skip = skipStatement(); + State = 94; _localctx.Skip = skipStatement(); } break; } @@ -399,27 +411,27 @@ public VariableDeclarationContext variableDeclaration() { try { EnterOuterAlt(_localctx, 1); { - State = 95; + State = 98; _la = _input.La(1); if (_la==ATTRIBUTE_ID) { { - State = 94; _localctx.Attributes = attributes(); + State = 97; _localctx.Attributes = attributes(); } } - State = 97; Match(VAR); - State = 98; _localctx.VariableName = Match(ID); - State = 101; + State = 100; Match(VAR); + State = 101; _localctx.VariableName = Match(ID); + State = 104; _la = _input.La(1); if (_la==8) { { - State = 99; Match(8); - State = 100; _localctx.VariableType = typeName(); + State = 102; Match(8); + State = 103; _localctx.VariableType = typeName(); } } - State = 103; Match(ASSIGN); - State = 104; _localctx.VariableDefaultValue = expression(0); + State = 106; Match(ASSIGN); + State = 107; _localctx.VariableDefaultValue = expression(0); } } catch (RecognitionException re) { @@ -495,35 +507,35 @@ private ExpressionContext expression(int _p) { int _alt; EnterOuterAlt(_localctx, 1); { - State = 112; + State = 115; switch ( Interpreter.AdaptivePredict(_input,10,_ctx) ) { case 1: { - State = 107; _localctx.Operator = Match(NOT); - State = 108; _localctx.Expression = expression(13); + State = 110; _localctx.Operator = Match(NOT); + State = 111; _localctx.Expression = expression(13); } break; case 2: { - State = 109; _localctx.PrimaryExpression = primaryExpression(); + State = 112; _localctx.PrimaryExpression = primaryExpression(); } break; case 3: { - State = 110; _localctx.FunctionCallExpression = functionCallExpression(); + State = 113; _localctx.FunctionCallExpression = functionCallExpression(); } break; case 4: { - State = 111; _localctx.MemberExpression = memberExpression(); + State = 114; _localctx.MemberExpression = memberExpression(); } break; } _ctx.stop = _input.Lt(-1); - State = 152; + State = 155; _errHandler.Sync(this); _alt = Interpreter.AdaptivePredict(_input,12,_ctx); while ( _alt!=2 && _alt!=-1 ) { @@ -531,17 +543,17 @@ private ExpressionContext expression(int _p) { if ( _parseListeners!=null ) TriggerExitRuleEvent(); _prevctx = _localctx; { - State = 150; + State = 153; switch ( Interpreter.AdaptivePredict(_input,11,_ctx) ) { case 1: { _localctx = new ExpressionContext(_parentctx, _parentState); _localctx.Left = _prevctx; PushNewRecursionContext(_localctx, _startState, RULE_expression); - State = 114; + State = 117; if (!(Precpred(_ctx, 12))) throw new FailedPredicateException(this, "Precpred(_ctx, 12)"); - State = 115; _localctx.Operator = Match(4); - State = 116; _localctx.Right = expression(13); + State = 118; _localctx.Operator = Match(4); + State = 119; _localctx.Right = expression(13); } break; @@ -550,10 +562,10 @@ private ExpressionContext expression(int _p) { _localctx = new ExpressionContext(_parentctx, _parentState); _localctx.Left = _prevctx; PushNewRecursionContext(_localctx, _startState, RULE_expression); - State = 117; + State = 120; if (!(Precpred(_ctx, 11))) throw new FailedPredicateException(this, "Precpred(_ctx, 11)"); - State = 118; _localctx.Operator = Match(6); - State = 119; _localctx.Right = expression(12); + State = 121; _localctx.Operator = Match(6); + State = 122; _localctx.Right = expression(12); } break; @@ -562,10 +574,10 @@ private ExpressionContext expression(int _p) { _localctx = new ExpressionContext(_parentctx, _parentState); _localctx.Left = _prevctx; PushNewRecursionContext(_localctx, _startState, RULE_expression); - State = 120; + State = 123; if (!(Precpred(_ctx, 10))) throw new FailedPredicateException(this, "Precpred(_ctx, 10)"); - State = 121; _localctx.Operator = Match(5); - State = 122; _localctx.Right = expression(11); + State = 124; _localctx.Operator = Match(5); + State = 125; _localctx.Right = expression(11); } break; @@ -574,10 +586,10 @@ private ExpressionContext expression(int _p) { _localctx = new ExpressionContext(_parentctx, _parentState); _localctx.Left = _prevctx; PushNewRecursionContext(_localctx, _startState, RULE_expression); - State = 123; + State = 126; if (!(Precpred(_ctx, 9))) throw new FailedPredicateException(this, "Precpred(_ctx, 9)"); - State = 124; _localctx.Operator = Match(17); - State = 125; _localctx.Right = expression(10); + State = 127; _localctx.Operator = Match(17); + State = 128; _localctx.Right = expression(10); } break; @@ -586,10 +598,10 @@ private ExpressionContext expression(int _p) { _localctx = new ExpressionContext(_parentctx, _parentState); _localctx.Left = _prevctx; PushNewRecursionContext(_localctx, _startState, RULE_expression); - State = 126; + State = 129; if (!(Precpred(_ctx, 8))) throw new FailedPredicateException(this, "Precpred(_ctx, 8)"); - State = 127; _localctx.Operator = Match(9); - State = 128; _localctx.Right = expression(9); + State = 130; _localctx.Operator = Match(9); + State = 131; _localctx.Right = expression(9); } break; @@ -598,10 +610,10 @@ private ExpressionContext expression(int _p) { _localctx = new ExpressionContext(_parentctx, _parentState); _localctx.Left = _prevctx; PushNewRecursionContext(_localctx, _startState, RULE_expression); - State = 129; + State = 132; if (!(Precpred(_ctx, 7))) throw new FailedPredicateException(this, "Precpred(_ctx, 7)"); - State = 130; _localctx.Operator = Match(14); - State = 131; _localctx.Right = expression(8); + State = 133; _localctx.Operator = Match(14); + State = 134; _localctx.Right = expression(8); } break; @@ -610,10 +622,10 @@ private ExpressionContext expression(int _p) { _localctx = new ExpressionContext(_parentctx, _parentState); _localctx.Left = _prevctx; PushNewRecursionContext(_localctx, _startState, RULE_expression); - State = 132; + State = 135; if (!(Precpred(_ctx, 6))) throw new FailedPredicateException(this, "Precpred(_ctx, 6)"); - State = 133; _localctx.Operator = Match(12); - State = 134; _localctx.Right = expression(7); + State = 136; _localctx.Operator = Match(12); + State = 137; _localctx.Right = expression(7); } break; @@ -622,10 +634,10 @@ private ExpressionContext expression(int _p) { _localctx = new ExpressionContext(_parentctx, _parentState); _localctx.Left = _prevctx; PushNewRecursionContext(_localctx, _startState, RULE_expression); - State = 135; + State = 138; if (!(Precpred(_ctx, 5))) throw new FailedPredicateException(this, "Precpred(_ctx, 5)"); - State = 136; _localctx.Operator = Match(18); - State = 137; _localctx.Right = expression(6); + State = 139; _localctx.Operator = Match(18); + State = 140; _localctx.Right = expression(6); } break; @@ -634,10 +646,10 @@ private ExpressionContext expression(int _p) { _localctx = new ExpressionContext(_parentctx, _parentState); _localctx.Left = _prevctx; PushNewRecursionContext(_localctx, _startState, RULE_expression); - State = 138; + State = 141; if (!(Precpred(_ctx, 4))) throw new FailedPredicateException(this, "Precpred(_ctx, 4)"); - State = 139; _localctx.Operator = Match(16); - State = 140; _localctx.Right = expression(5); + State = 142; _localctx.Operator = Match(16); + State = 143; _localctx.Right = expression(5); } break; @@ -646,10 +658,10 @@ private ExpressionContext expression(int _p) { _localctx = new ExpressionContext(_parentctx, _parentState); _localctx.Left = _prevctx; PushNewRecursionContext(_localctx, _startState, RULE_expression); - State = 141; + State = 144; if (!(Precpred(_ctx, 3))) throw new FailedPredicateException(this, "Precpred(_ctx, 3)"); - State = 142; _localctx.Operator = Match(10); - State = 143; _localctx.Right = expression(4); + State = 145; _localctx.Operator = Match(10); + State = 146; _localctx.Right = expression(4); } break; @@ -658,10 +670,10 @@ private ExpressionContext expression(int _p) { _localctx = new ExpressionContext(_parentctx, _parentState); _localctx.Left = _prevctx; PushNewRecursionContext(_localctx, _startState, RULE_expression); - State = 144; + State = 147; if (!(Precpred(_ctx, 2))) throw new FailedPredicateException(this, "Precpred(_ctx, 2)"); - State = 145; _localctx.Operator = Match(AND); - State = 146; _localctx.Right = expression(3); + State = 148; _localctx.Operator = Match(AND); + State = 149; _localctx.Right = expression(3); } break; @@ -670,16 +682,16 @@ private ExpressionContext expression(int _p) { _localctx = new ExpressionContext(_parentctx, _parentState); _localctx.Left = _prevctx; PushNewRecursionContext(_localctx, _startState, RULE_expression); - State = 147; + State = 150; if (!(Precpred(_ctx, 1))) throw new FailedPredicateException(this, "Precpred(_ctx, 1)"); - State = 148; _localctx.Operator = Match(OR); - State = 149; _localctx.Right = expression(2); + State = 151; _localctx.Operator = Match(OR); + State = 152; _localctx.Right = expression(2); } break; } } } - State = 154; + State = 157; _errHandler.Sync(this); _alt = Interpreter.AdaptivePredict(_input,12,_ctx); } @@ -697,12 +709,13 @@ private ExpressionContext expression(int _p) { } public partial class FunctionCallExpressionContext : ParserRuleContext { + public ITerminalNode ID() { return GetToken(MetaCodeParser.ID, 0); } + public FunctionExpressionContext functionExpression() { + return GetRuleContext(0); + } public ExpressionContext expression() { return GetRuleContext(0); } - public PrimaryExpressionContext primaryExpression() { - return GetRuleContext(0); - } public FunctionCallExpressionContext(ParserRuleContext parent, int invokingState) : base(parent, invokingState) { @@ -731,17 +744,31 @@ public FunctionCallExpressionContext functionCallExpression() { try { EnterOuterAlt(_localctx, 1); { - State = 155; primaryExpression(); - State = 156; Match(LEFT_PARENTHESIS); - State = 158; + State = 160; + switch (_input.La(1)) { + case ID: + { + State = 158; Match(ID); + } + break; + case FUNCTION: + { + State = 159; functionExpression(); + } + break; + default: + throw new NoViableAltException(this); + } + State = 162; Match(LEFT_PARENTHESIS); + State = 164; _la = _input.La(1); if ((((_la) & ~0x3f) == 0 && ((1L << _la) & ((1L << 7) | (1L << FUNCTION) | (1L << BOOLEAN) | (1L << NOT) | (1L << LEFT_PARENTHESIS) | (1L << ID) | (1L << ATTRIBUTE_ID) | (1L << STRING) | (1L << NUMBER))) != 0)) { { - State = 157; expression(0); + State = 163; expression(0); } } - State = 160; Match(RIGHT_PARENTHESIS); + State = 166; Match(RIGHT_PARENTHESIS); } } catch (RecognitionException re) { @@ -756,20 +783,17 @@ public FunctionCallExpressionContext functionCallExpression() { } public partial class MemberExpressionContext : ParserRuleContext { - public FunctionCallExpressionContext functionCallExpression(int i) { - return GetRuleContext(i); + public MemberTagExpressionContext memberTagExpression(int i) { + return GetRuleContext(i); } - public IReadOnlyList functionCallExpression() { - return GetRuleContexts(); - } - public IdentifierContext identifier(int i) { - return GetRuleContext(i); + public FunctionCallExpressionContext functionCallExpression() { + return GetRuleContext(0); } public PrimaryExpressionContext primaryExpression() { return GetRuleContext(0); } - public IReadOnlyList identifier() { - return GetRuleContexts(); + public IReadOnlyList memberTagExpression() { + return GetRuleContexts(); } public MemberExpressionContext(ParserRuleContext parent, int invokingState) : base(parent, invokingState) @@ -799,39 +823,39 @@ public MemberExpressionContext memberExpression() { int _alt; EnterOuterAlt(_localctx, 1); { - State = 162; primaryExpression(); - State = 168; + State = 170; + switch ( Interpreter.AdaptivePredict(_input,15,_ctx) ) { + case 1: + { + State = 168; primaryExpression(); + } + break; + + case 2: + { + State = 169; functionCallExpression(); + } + break; + } + State = 174; _errHandler.Sync(this); - _alt = Interpreter.AdaptivePredict(_input,15,_ctx); + _alt = Interpreter.AdaptivePredict(_input,16,_ctx); do { switch (_alt) { case 1: { { - State = 163; Match(2); - State = 166; - switch ( Interpreter.AdaptivePredict(_input,14,_ctx) ) { - case 1: - { - State = 164; identifier(); - } - break; - - case 2: - { - State = 165; functionCallExpression(); - } - break; - } + State = 172; Match(2); + State = 173; memberTagExpression(); } } break; default: throw new NoViableAltException(this); } - State = 170; + State = 176; _errHandler.Sync(this); - _alt = Interpreter.AdaptivePredict(_input,15,_ctx); + _alt = Interpreter.AdaptivePredict(_input,16,_ctx); } while ( _alt!=2 && _alt!=-1 ); } } @@ -846,16 +870,75 @@ public MemberExpressionContext memberExpression() { return _localctx; } + public partial class MemberTagExpressionContext : ParserRuleContext { + public FunctionCallExpressionContext functionCallExpression() { + return GetRuleContext(0); + } + public ITerminalNode ID() { return GetToken(MetaCodeParser.ID, 0); } + public MemberTagExpressionContext(ParserRuleContext parent, int invokingState) + : base(parent, invokingState) + { + } + public override int GetRuleIndex() { return RULE_memberTagExpression; } + public override void EnterRule(IParseTreeListener listener) { + IMetaCodeListener typedListener = listener as IMetaCodeListener; + if (typedListener != null) typedListener.EnterMemberTagExpression(this); + } + public override void ExitRule(IParseTreeListener listener) { + IMetaCodeListener typedListener = listener as IMetaCodeListener; + if (typedListener != null) typedListener.ExitMemberTagExpression(this); + } + public override TResult Accept(IParseTreeVisitor visitor) { + IMetaCodeVisitor typedVisitor = visitor as IMetaCodeVisitor; + if (typedVisitor != null) return typedVisitor.VisitMemberTagExpression(this); + else return visitor.VisitChildren(this); + } + } + + [RuleVersion(0)] + public MemberTagExpressionContext memberTagExpression() { + MemberTagExpressionContext _localctx = new MemberTagExpressionContext(_ctx, State); + EnterRule(_localctx, 14, RULE_memberTagExpression); + try { + State = 180; + switch ( Interpreter.AdaptivePredict(_input,17,_ctx) ) { + case 1: + EnterOuterAlt(_localctx, 1); + { + State = 178; Match(ID); + } + break; + + case 2: + EnterOuterAlt(_localctx, 2); + { + State = 179; functionCallExpression(); + } + break; + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.ReportError(this, re); + _errHandler.Recover(this, re); + } + finally { + ExitRule(); + } + return _localctx; + } + public partial class PrimaryExpressionContext : ParserRuleContext { public AttributesContext Attributes; public ConstantContext Constant; - public IdentifierContext Id; + public IToken Id; public FunctionExpressionContext Function; public AssignmentExpressionContext Assignment; public ExpressionContext InnerExpression; public AttributesContext attributes() { return GetRuleContext(0); } + public ITerminalNode ID() { return GetToken(MetaCodeParser.ID, 0); } public FunctionExpressionContext functionExpression() { return GetRuleContext(0); } @@ -868,9 +951,6 @@ public AssignmentExpressionContext assignmentExpression() { public ConstantContext constant() { return GetRuleContext(0); } - public IdentifierContext identifier() { - return GetRuleContext(0); - } public PrimaryExpressionContext(ParserRuleContext parent, int invokingState) : base(parent, invokingState) { @@ -894,85 +974,85 @@ public override TResult Accept(IParseTreeVisitor visitor) { [RuleVersion(0)] public PrimaryExpressionContext primaryExpression() { PrimaryExpressionContext _localctx = new PrimaryExpressionContext(_ctx, State); - EnterRule(_localctx, 14, RULE_primaryExpression); + EnterRule(_localctx, 16, RULE_primaryExpression); int _la; try { - State = 195; - switch ( Interpreter.AdaptivePredict(_input,21,_ctx) ) { + State = 205; + switch ( Interpreter.AdaptivePredict(_input,23,_ctx) ) { case 1: EnterOuterAlt(_localctx, 1); { - State = 173; + State = 183; _la = _input.La(1); if (_la==ATTRIBUTE_ID) { { - State = 172; _localctx.Attributes = attributes(); + State = 182; _localctx.Attributes = attributes(); } } - State = 175; _localctx.Constant = constant(); + State = 185; _localctx.Constant = constant(); } break; case 2: EnterOuterAlt(_localctx, 2); { - State = 177; + State = 187; _la = _input.La(1); if (_la==ATTRIBUTE_ID) { { - State = 176; _localctx.Attributes = attributes(); + State = 186; _localctx.Attributes = attributes(); } } - State = 179; _localctx.Id = identifier(); + State = 189; _localctx.Id = Match(ID); } break; case 3: EnterOuterAlt(_localctx, 3); { - State = 181; + State = 191; _la = _input.La(1); if (_la==ATTRIBUTE_ID) { { - State = 180; _localctx.Attributes = attributes(); + State = 190; _localctx.Attributes = attributes(); } } - State = 183; _localctx.Function = functionExpression(); + State = 193; _localctx.Function = functionExpression(); } break; case 4: EnterOuterAlt(_localctx, 4); { - State = 185; + State = 195; _la = _input.La(1); if (_la==ATTRIBUTE_ID) { { - State = 184; _localctx.Attributes = attributes(); + State = 194; _localctx.Attributes = attributes(); } } - State = 187; _localctx.Assignment = assignmentExpression(); + State = 197; _localctx.Assignment = assignmentExpression(); } break; case 5: EnterOuterAlt(_localctx, 5); { - State = 189; + State = 199; _la = _input.La(1); if (_la==ATTRIBUTE_ID) { { - State = 188; _localctx.Attributes = attributes(); + State = 198; _localctx.Attributes = attributes(); } } - State = 191; Match(LEFT_PARENTHESIS); - State = 192; _localctx.InnerExpression = expression(0); - State = 193; Match(RIGHT_PARENTHESIS); + State = 201; Match(LEFT_PARENTHESIS); + State = 202; _localctx.InnerExpression = expression(0); + State = 203; Match(RIGHT_PARENTHESIS); } break; } @@ -989,7 +1069,7 @@ public PrimaryExpressionContext primaryExpression() { } public partial class FunctionExpressionContext : ParserRuleContext { - public IdentifierContext FunctionName; + public IToken FunctionName; public FormalParameterListContext Parameters; public TypeNameContext ReturnType; public StatementsContext BodyStatements; @@ -1001,6 +1081,7 @@ public StatementsContext statements() { return GetRuleContext(0); } public ITerminalNode DO() { return GetToken(MetaCodeParser.DO, 0); } + public ITerminalNode ID() { return GetToken(MetaCodeParser.ID, 0); } public ITerminalNode FUNCTION() { return GetToken(MetaCodeParser.FUNCTION, 0); } public ExpressionContext expression() { return GetRuleContext(0); @@ -1008,9 +1089,6 @@ public ExpressionContext expression() { public FormalParameterListContext formalParameterList() { return GetRuleContext(0); } - public IdentifierContext identifier() { - return GetRuleContext(0); - } public ITerminalNode END() { return GetToken(MetaCodeParser.END, 0); } public FunctionExpressionContext(ParserRuleContext parent, int invokingState) : base(parent, invokingState) @@ -1035,81 +1113,71 @@ public override TResult Accept(IParseTreeVisitor visitor) { [RuleVersion(0)] public FunctionExpressionContext functionExpression() { FunctionExpressionContext _localctx = new FunctionExpressionContext(_ctx, State); - EnterRule(_localctx, 16, RULE_functionExpression); + EnterRule(_localctx, 18, RULE_functionExpression); int _la; try { - State = 229; + State = 238; switch ( Interpreter.AdaptivePredict(_input,28,_ctx) ) { case 1: EnterOuterAlt(_localctx, 1); { - State = 197; Match(FUNCTION); - State = 199; + State = 207; Match(FUNCTION); + State = 209; _la = _input.La(1); if (_la==ID) { { - State = 198; _localctx.FunctionName = identifier(); + State = 208; _localctx.FunctionName = Match(ID); } } - State = 201; Match(LEFT_PARENTHESIS); - State = 203; + State = 211; Match(LEFT_PARENTHESIS); + State = 213; _la = _input.La(1); if (_la==ID || _la==ATTRIBUTE_ID) { { - State = 202; _localctx.Parameters = formalParameterList(); + State = 212; _localctx.Parameters = formalParameterList(); } } - State = 205; Match(RIGHT_PARENTHESIS); - State = 208; - _la = _input.La(1); - if (_la==8) { - { - State = 206; Match(8); - State = 207; _localctx.ReturnType = typeName(); - } + State = 215; Match(RIGHT_PARENTHESIS); + { + State = 216; Match(8); + State = 217; _localctx.ReturnType = typeName(); } - - State = 210; Match(DO); - State = 211; _localctx.BodyStatements = statements(); - State = 212; Match(END); + State = 219; Match(DO); + State = 220; _localctx.BodyStatements = statements(); + State = 221; Match(END); } break; case 2: EnterOuterAlt(_localctx, 2); { - State = 214; Match(FUNCTION); - State = 216; + State = 223; Match(FUNCTION); + State = 225; _la = _input.La(1); if (_la==ID) { { - State = 215; _localctx.FunctionName = identifier(); + State = 224; _localctx.FunctionName = Match(ID); } } - State = 218; Match(LEFT_PARENTHESIS); - State = 220; + State = 227; Match(LEFT_PARENTHESIS); + State = 229; _la = _input.La(1); if (_la==ID || _la==ATTRIBUTE_ID) { { - State = 219; _localctx.Parameters = formalParameterList(); + State = 228; _localctx.Parameters = formalParameterList(); } } - State = 222; Match(RIGHT_PARENTHESIS); - State = 225; - _la = _input.La(1); - if (_la==8) { - { - State = 223; Match(8); - State = 224; _localctx.ReturnType = typeName(); - } + State = 231; Match(RIGHT_PARENTHESIS); + { + State = 232; Match(8); + State = 233; _localctx.ReturnType = typeName(); } - - State = 227; Match(ASSIGN); - State = 228; _localctx.BodyExpression = expression(0); + State = 235; Match(ASSIGN); + State = 236; _localctx.BodyExpression = expression(0); } break; } @@ -1127,7 +1195,7 @@ public FunctionExpressionContext functionExpression() { public partial class ForeachStatementContext : ParserRuleContext { public IToken Var; - public IdentifierContext Id; + public IToken Id; public TypeNameContext VariableType; public ExpressionContext ArrayExpression; public StatementContext Body; @@ -1139,13 +1207,11 @@ public TypeNameContext typeName() { return GetRuleContext(0); } public ITerminalNode VAR() { return GetToken(MetaCodeParser.VAR, 0); } + public ITerminalNode ID() { return GetToken(MetaCodeParser.ID, 0); } public ExpressionContext expression() { return GetRuleContext(0); } public ITerminalNode FOREACH() { return GetToken(MetaCodeParser.FOREACH, 0); } - public IdentifierContext identifier() { - return GetRuleContext(0); - } public ForeachStatementContext(ParserRuleContext parent, int invokingState) : base(parent, invokingState) { @@ -1169,35 +1235,35 @@ public override TResult Accept(IParseTreeVisitor visitor) { [RuleVersion(0)] public ForeachStatementContext foreachStatement() { ForeachStatementContext _localctx = new ForeachStatementContext(_ctx, State); - EnterRule(_localctx, 18, RULE_foreachStatement); + EnterRule(_localctx, 20, RULE_foreachStatement); int _la; try { EnterOuterAlt(_localctx, 1); { - State = 231; Match(FOREACH); - State = 232; Match(LEFT_PARENTHESIS); - State = 234; + State = 240; Match(FOREACH); + State = 241; Match(LEFT_PARENTHESIS); + State = 243; _la = _input.La(1); if (_la==VAR) { { - State = 233; _localctx.Var = Match(VAR); + State = 242; _localctx.Var = Match(VAR); } } - State = 236; _localctx.Id = identifier(); - State = 239; + State = 245; _localctx.Id = Match(ID); + State = 248; _la = _input.La(1); if (_la==8) { { - State = 237; Match(8); - State = 238; _localctx.VariableType = typeName(); + State = 246; Match(8); + State = 247; _localctx.VariableType = typeName(); } } - State = 241; Match(IN); - State = 242; _localctx.ArrayExpression = expression(0); - State = 243; Match(RIGHT_PARENTHESIS); - State = 244; _localctx.Body = statement(); + State = 250; Match(IN); + State = 251; _localctx.ArrayExpression = expression(0); + State = 252; Match(RIGHT_PARENTHESIS); + State = 253; _localctx.Body = statement(); } } catch (RecognitionException re) { @@ -1244,15 +1310,15 @@ public override TResult Accept(IParseTreeVisitor visitor) { [RuleVersion(0)] public WhileStatementContext whileStatement() { WhileStatementContext _localctx = new WhileStatementContext(_ctx, State); - EnterRule(_localctx, 20, RULE_whileStatement); + EnterRule(_localctx, 22, RULE_whileStatement); try { EnterOuterAlt(_localctx, 1); { - State = 246; Match(WHILE); - State = 247; Match(LEFT_PARENTHESIS); - State = 248; _localctx.ConditionExpression = expression(0); - State = 249; Match(RIGHT_PARENTHESIS); - State = 250; _localctx.Body = statement(); + State = 255; Match(WHILE); + State = 256; Match(LEFT_PARENTHESIS); + State = 257; _localctx.ConditionExpression = expression(0); + State = 258; Match(RIGHT_PARENTHESIS); + State = 259; _localctx.Body = statement(); } } catch (RecognitionException re) { @@ -1296,13 +1362,13 @@ public override TResult Accept(IParseTreeVisitor visitor) { [RuleVersion(0)] public BlockStatementContext blockStatement() { BlockStatementContext _localctx = new BlockStatementContext(_ctx, State); - EnterRule(_localctx, 22, RULE_blockStatement); + EnterRule(_localctx, 24, RULE_blockStatement); try { EnterOuterAlt(_localctx, 1); { - State = 252; Match(DO); - State = 253; _localctx.Body = statements(); - State = 254; Match(END); + State = 261; Match(DO); + State = 262; _localctx.Body = statements(); + State = 263; Match(END); } } catch (RecognitionException re) { @@ -1341,11 +1407,58 @@ public override TResult Accept(IParseTreeVisitor visitor) { [RuleVersion(0)] public SkipStatementContext skipStatement() { SkipStatementContext _localctx = new SkipStatementContext(_ctx, State); - EnterRule(_localctx, 24, RULE_skipStatement); + EnterRule(_localctx, 26, RULE_skipStatement); + try { + EnterOuterAlt(_localctx, 1); + { + State = 265; Match(SKIP); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.ReportError(this, re); + _errHandler.Recover(this, re); + } + finally { + ExitRule(); + } + return _localctx; + } + + public partial class ReturnStatementContext : ParserRuleContext { + public ITerminalNode RETURN() { return GetToken(MetaCodeParser.RETURN, 0); } + public ExpressionContext expression() { + return GetRuleContext(0); + } + public ReturnStatementContext(ParserRuleContext parent, int invokingState) + : base(parent, invokingState) + { + } + public override int GetRuleIndex() { return RULE_returnStatement; } + public override void EnterRule(IParseTreeListener listener) { + IMetaCodeListener typedListener = listener as IMetaCodeListener; + if (typedListener != null) typedListener.EnterReturnStatement(this); + } + public override void ExitRule(IParseTreeListener listener) { + IMetaCodeListener typedListener = listener as IMetaCodeListener; + if (typedListener != null) typedListener.ExitReturnStatement(this); + } + public override TResult Accept(IParseTreeVisitor visitor) { + IMetaCodeVisitor typedVisitor = visitor as IMetaCodeVisitor; + if (typedVisitor != null) return typedVisitor.VisitReturnStatement(this); + else return visitor.VisitChildren(this); + } + } + + [RuleVersion(0)] + public ReturnStatementContext returnStatement() { + ReturnStatementContext _localctx = new ReturnStatementContext(_ctx, State); + EnterRule(_localctx, 28, RULE_returnStatement); try { EnterOuterAlt(_localctx, 1); { - State = 256; Match(SKIP); + State = 267; Match(RETURN); + State = 268; expression(0); } } catch (RecognitionException re) { @@ -1360,7 +1473,7 @@ public SkipStatementContext skipStatement() { } public partial class AssignmentExpressionContext : ParserRuleContext { - public IdentifierContext Variable; + public IToken Variable; public ExpressionContext Value; public AttributesContext ConditionalAttributes; public ExpressionContext ConditionalExpression; @@ -1372,12 +1485,10 @@ public ExpressionContext expression(int i) { return GetRuleContext(i); } public ITerminalNode ASSIGN() { return GetToken(MetaCodeParser.ASSIGN, 0); } + public ITerminalNode ID() { return GetToken(MetaCodeParser.ID, 0); } public IReadOnlyList expression() { return GetRuleContexts(); } - public IdentifierContext identifier() { - return GetRuleContext(0); - } public AssignmentExpressionContext(ParserRuleContext parent, int invokingState) : base(parent, invokingState) { @@ -1401,30 +1512,30 @@ public override TResult Accept(IParseTreeVisitor visitor) { [RuleVersion(0)] public AssignmentExpressionContext assignmentExpression() { AssignmentExpressionContext _localctx = new AssignmentExpressionContext(_ctx, State); - EnterRule(_localctx, 26, RULE_assignmentExpression); + EnterRule(_localctx, 30, RULE_assignmentExpression); int _la; try { EnterOuterAlt(_localctx, 1); { - State = 258; _localctx.Variable = identifier(); - State = 259; Match(ASSIGN); - State = 260; _localctx.Value = expression(0); - State = 269; + State = 270; _localctx.Variable = Match(ID); + State = 271; Match(ASSIGN); + State = 272; _localctx.Value = expression(0); + State = 281; switch ( Interpreter.AdaptivePredict(_input,32,_ctx) ) { case 1: { - State = 262; + State = 274; _la = _input.La(1); if (_la==ATTRIBUTE_ID) { { - State = 261; _localctx.ConditionalAttributes = attributes(); + State = 273; _localctx.ConditionalAttributes = attributes(); } } - State = 264; Match(IF); - State = 265; Match(LEFT_PARENTHESIS); - State = 266; _localctx.ConditionalExpression = expression(0); - State = 267; Match(RIGHT_PARENTHESIS); + State = 276; Match(IF); + State = 277; Match(LEFT_PARENTHESIS); + State = 278; _localctx.ConditionalExpression = expression(0); + State = 279; Match(RIGHT_PARENTHESIS); } break; } @@ -1444,7 +1555,7 @@ public AssignmentExpressionContext assignmentExpression() { public partial class IfStatementContext : ParserRuleContext { public ExpressionContext Condition; public StatementsContext Statements; - public ElseIfStatementContext ElseIfExpressions; + public ElseIfStatementContext ElseIfStatements; public StatementsContext ElseStatements; public ITerminalNode IF() { return GetToken(MetaCodeParser.IF, 0); } public IReadOnlyList statements() { @@ -1487,42 +1598,42 @@ public override TResult Accept(IParseTreeVisitor visitor) { [RuleVersion(0)] public IfStatementContext ifStatement() { IfStatementContext _localctx = new IfStatementContext(_ctx, State); - EnterRule(_localctx, 28, RULE_ifStatement); + EnterRule(_localctx, 32, RULE_ifStatement); int _la; try { int _alt; EnterOuterAlt(_localctx, 1); { - State = 271; Match(IF); - State = 272; Match(LEFT_PARENTHESIS); - State = 273; _localctx.Condition = expression(0); - State = 274; Match(RIGHT_PARENTHESIS); - State = 275; _localctx.Statements = statements(); - State = 279; + State = 283; Match(IF); + State = 284; Match(LEFT_PARENTHESIS); + State = 285; _localctx.Condition = expression(0); + State = 286; Match(RIGHT_PARENTHESIS); + State = 287; _localctx.Statements = statements(); + State = 291; _errHandler.Sync(this); _alt = Interpreter.AdaptivePredict(_input,33,_ctx); while ( _alt!=2 && _alt!=-1 ) { if ( _alt==1 ) { { { - State = 276; _localctx.ElseIfExpressions = elseIfStatement(); + State = 288; _localctx.ElseIfStatements = elseIfStatement(); } } } - State = 281; + State = 293; _errHandler.Sync(this); _alt = Interpreter.AdaptivePredict(_input,33,_ctx); } - State = 284; + State = 296; _la = _input.La(1); if (_la==ELSE) { { - State = 282; Match(ELSE); - State = 283; _localctx.ElseStatements = statements(); + State = 294; Match(ELSE); + State = 295; _localctx.ElseStatements = statements(); } } - State = 286; Match(END); + State = 298; Match(END); } } catch (RecognitionException re) { @@ -1537,6 +1648,8 @@ public IfStatementContext ifStatement() { } public partial class ElseIfStatementContext : ParserRuleContext { + public ExpressionContext Condition; + public StatementsContext Statements; public ITerminalNode IF() { return GetToken(MetaCodeParser.IF, 0); } public StatementsContext statements() { return GetRuleContext(0); @@ -1568,16 +1681,16 @@ public override TResult Accept(IParseTreeVisitor visitor) { [RuleVersion(0)] public ElseIfStatementContext elseIfStatement() { ElseIfStatementContext _localctx = new ElseIfStatementContext(_ctx, State); - EnterRule(_localctx, 30, RULE_elseIfStatement); + EnterRule(_localctx, 34, RULE_elseIfStatement); try { EnterOuterAlt(_localctx, 1); { - State = 288; Match(ELSE); - State = 289; Match(IF); - State = 290; Match(LEFT_PARENTHESIS); - State = 291; expression(0); - State = 292; Match(RIGHT_PARENTHESIS); - State = 293; statements(); + State = 300; Match(ELSE); + State = 301; Match(IF); + State = 302; Match(LEFT_PARENTHESIS); + State = 303; _localctx.Condition = expression(0); + State = 304; Match(RIGHT_PARENTHESIS); + State = 305; _localctx.Statements = statements(); } } catch (RecognitionException re) { @@ -1621,23 +1734,23 @@ public override TResult Accept(IParseTreeVisitor visitor) { [RuleVersion(0)] public FormalParameterListContext formalParameterList() { FormalParameterListContext _localctx = new FormalParameterListContext(_ctx, State); - EnterRule(_localctx, 32, RULE_formalParameterList); + EnterRule(_localctx, 36, RULE_formalParameterList); int _la; try { EnterOuterAlt(_localctx, 1); { - State = 295; formalParameter(); - State = 300; + State = 307; formalParameter(); + State = 312; _errHandler.Sync(this); _la = _input.La(1); while (_la==3) { { { - State = 296; Match(3); - State = 297; formalParameter(); + State = 308; Match(3); + State = 309; formalParameter(); } } - State = 302; + State = 314; _errHandler.Sync(this); _la = _input.La(1); } @@ -1655,15 +1768,16 @@ public FormalParameterListContext formalParameterList() { } public partial class FormalParameterContext : ParserRuleContext { + public AttributesContext Attributes; + public IToken Name; + public TypeNameContext Type; public AttributesContext attributes() { return GetRuleContext(0); } public TypeNameContext typeName() { return GetRuleContext(0); } - public IdentifierContext identifier() { - return GetRuleContext(0); - } + public ITerminalNode ID() { return GetToken(MetaCodeParser.ID, 0); } public FormalParameterContext(ParserRuleContext parent, int invokingState) : base(parent, invokingState) { @@ -1687,22 +1801,22 @@ public override TResult Accept(IParseTreeVisitor visitor) { [RuleVersion(0)] public FormalParameterContext formalParameter() { FormalParameterContext _localctx = new FormalParameterContext(_ctx, State); - EnterRule(_localctx, 34, RULE_formalParameter); + EnterRule(_localctx, 38, RULE_formalParameter); int _la; try { EnterOuterAlt(_localctx, 1); { - State = 304; + State = 316; _la = _input.La(1); if (_la==ATTRIBUTE_ID) { { - State = 303; attributes(); + State = 315; _localctx.Attributes = attributes(); } } - State = 306; identifier(); - State = 307; Match(8); - State = 308; typeName(); + State = 318; _localctx.Name = Match(ID); + State = 319; Match(8); + State = 320; _localctx.Type = typeName(); } } catch (RecognitionException re) { @@ -1746,23 +1860,23 @@ public override TResult Accept(IParseTreeVisitor visitor) { [RuleVersion(0)] public ActualParameterListContext actualParameterList() { ActualParameterListContext _localctx = new ActualParameterListContext(_ctx, State); - EnterRule(_localctx, 36, RULE_actualParameterList); + EnterRule(_localctx, 40, RULE_actualParameterList); int _la; try { EnterOuterAlt(_localctx, 1); { - State = 310; expression(0); - State = 315; + State = 322; expression(0); + State = 327; _errHandler.Sync(this); _la = _input.La(1); while (_la==3) { { { - State = 311; Match(3); - State = 312; expression(0); + State = 323; Match(3); + State = 324; expression(0); } } - State = 317; + State = 329; _errHandler.Sync(this); _la = _input.La(1); } @@ -1810,31 +1924,31 @@ public override TResult Accept(IParseTreeVisitor visitor) { [RuleVersion(0)] public TypeNameContext typeName() { TypeNameContext _localctx = new TypeNameContext(_ctx, State); - EnterRule(_localctx, 38, RULE_typeName); + EnterRule(_localctx, 42, RULE_typeName); int _la; try { EnterOuterAlt(_localctx, 1); { - State = 319; + State = 331; _la = _input.La(1); if (_la==ATTRIBUTE_ID) { { - State = 318; attributes(); + State = 330; attributes(); } } - State = 321; Match(ID); - State = 326; + State = 333; Match(ID); + State = 338; _errHandler.Sync(this); _la = _input.La(1); while (_la==2) { { { - State = 322; Match(2); - State = 323; Match(ID); + State = 334; Match(2); + State = 335; Match(ID); } } - State = 328; + State = 340; _errHandler.Sync(this); _la = _input.La(1); } @@ -1895,42 +2009,42 @@ public override TResult Accept(IParseTreeVisitor visitor) { [RuleVersion(0)] public ConstantContext constant() { ConstantContext _localctx = new ConstantContext(_ctx, State); - EnterRule(_localctx, 40, RULE_constant); + EnterRule(_localctx, 44, RULE_constant); try { - State = 334; + State = 346; switch ( Interpreter.AdaptivePredict(_input,40,_ctx) ) { case 1: EnterOuterAlt(_localctx, 1); { - State = 329; _localctx.Number = numberConstant(); + State = 341; _localctx.Number = numberConstant(); } break; case 2: EnterOuterAlt(_localctx, 2); { - State = 330; _localctx.String = stringConstant(); + State = 342; _localctx.String = stringConstant(); } break; case 3: EnterOuterAlt(_localctx, 3); { - State = 331; _localctx.Boolean = booleanConstant(); + State = 343; _localctx.Boolean = booleanConstant(); } break; case 4: EnterOuterAlt(_localctx, 4); { - State = 332; _localctx.Array = arrayConstant(); + State = 344; _localctx.Array = arrayConstant(); } break; case 5: EnterOuterAlt(_localctx, 5); { - State = 333; _localctx.Interval = intervalConstant(); + State = 345; _localctx.Interval = intervalConstant(); } break; } @@ -1946,50 +2060,6 @@ public ConstantContext constant() { return _localctx; } - public partial class IdentifierContext : ParserRuleContext { - public IToken Id; - public ITerminalNode ID() { return GetToken(MetaCodeParser.ID, 0); } - public IdentifierContext(ParserRuleContext parent, int invokingState) - : base(parent, invokingState) - { - } - public override int GetRuleIndex() { return RULE_identifier; } - public override void EnterRule(IParseTreeListener listener) { - IMetaCodeListener typedListener = listener as IMetaCodeListener; - if (typedListener != null) typedListener.EnterIdentifier(this); - } - public override void ExitRule(IParseTreeListener listener) { - IMetaCodeListener typedListener = listener as IMetaCodeListener; - if (typedListener != null) typedListener.ExitIdentifier(this); - } - public override TResult Accept(IParseTreeVisitor visitor) { - IMetaCodeVisitor typedVisitor = visitor as IMetaCodeVisitor; - if (typedVisitor != null) return typedVisitor.VisitIdentifier(this); - else return visitor.VisitChildren(this); - } - } - - [RuleVersion(0)] - public IdentifierContext identifier() { - IdentifierContext _localctx = new IdentifierContext(_ctx, State); - EnterRule(_localctx, 42, RULE_identifier); - try { - EnterOuterAlt(_localctx, 1); - { - State = 336; _localctx.Id = Match(ID); - } - } - catch (RecognitionException re) { - _localctx.exception = re; - _errHandler.ReportError(this, re); - _errHandler.Recover(this, re); - } - finally { - ExitRule(); - } - return _localctx; - } - public partial class NumberConstantContext : ParserRuleContext { public ITerminalNode NUMBER() { return GetToken(MetaCodeParser.NUMBER, 0); } public NumberConstantContext(ParserRuleContext parent, int invokingState) @@ -2015,11 +2085,11 @@ public override TResult Accept(IParseTreeVisitor visitor) { [RuleVersion(0)] public NumberConstantContext numberConstant() { NumberConstantContext _localctx = new NumberConstantContext(_ctx, State); - EnterRule(_localctx, 44, RULE_numberConstant); + EnterRule(_localctx, 46, RULE_numberConstant); try { EnterOuterAlt(_localctx, 1); { - State = 338; Match(NUMBER); + State = 348; Match(NUMBER); } } catch (RecognitionException re) { @@ -2058,11 +2128,11 @@ public override TResult Accept(IParseTreeVisitor visitor) { [RuleVersion(0)] public StringConstantContext stringConstant() { StringConstantContext _localctx = new StringConstantContext(_ctx, State); - EnterRule(_localctx, 46, RULE_stringConstant); + EnterRule(_localctx, 48, RULE_stringConstant); try { EnterOuterAlt(_localctx, 1); { - State = 340; Match(STRING); + State = 350; Match(STRING); } } catch (RecognitionException re) { @@ -2101,11 +2171,11 @@ public override TResult Accept(IParseTreeVisitor visitor) { [RuleVersion(0)] public BooleanConstantContext booleanConstant() { BooleanConstantContext _localctx = new BooleanConstantContext(_ctx, State); - EnterRule(_localctx, 48, RULE_booleanConstant); + EnterRule(_localctx, 50, RULE_booleanConstant); try { EnterOuterAlt(_localctx, 1); { - State = 342; Match(BOOLEAN); + State = 352; Match(BOOLEAN); } } catch (RecognitionException re) { @@ -2149,39 +2219,39 @@ public override TResult Accept(IParseTreeVisitor visitor) { [RuleVersion(0)] public ArrayConstantContext arrayConstant() { ArrayConstantContext _localctx = new ArrayConstantContext(_ctx, State); - EnterRule(_localctx, 50, RULE_arrayConstant); + EnterRule(_localctx, 52, RULE_arrayConstant); int _la; try { - State = 357; + State = 367; switch ( Interpreter.AdaptivePredict(_input,42,_ctx) ) { case 1: EnterOuterAlt(_localctx, 1); { - State = 344; Match(7); - State = 345; expression(0); - State = 350; + State = 354; Match(7); + State = 355; expression(0); + State = 360; _errHandler.Sync(this); _la = _input.La(1); while (_la==3) { { { - State = 346; Match(3); - State = 347; expression(0); + State = 356; Match(3); + State = 357; expression(0); } } - State = 352; + State = 362; _errHandler.Sync(this); _la = _input.La(1); } - State = 353; Match(1); + State = 363; Match(1); } break; case 2: EnterOuterAlt(_localctx, 2); { - State = 355; Match(7); - State = 356; Match(1); + State = 365; Match(7); + State = 366; Match(1); } break; } @@ -2228,19 +2298,19 @@ public override TResult Accept(IParseTreeVisitor visitor) { [RuleVersion(0)] public IntervalConstantContext intervalConstant() { IntervalConstantContext _localctx = new IntervalConstantContext(_ctx, State); - EnterRule(_localctx, 52, RULE_intervalConstant); + EnterRule(_localctx, 54, RULE_intervalConstant); try { EnterOuterAlt(_localctx, 1); { - State = 359; _localctx.Start = Match(NUMBER); - State = 360; Match(13); - State = 361; _localctx.End = Match(NUMBER); - State = 364; + State = 369; _localctx.Start = Match(NUMBER); + State = 370; Match(13); + State = 371; _localctx.End = Match(NUMBER); + State = 374; switch ( Interpreter.AdaptivePredict(_input,43,_ctx) ) { case 1: { - State = 362; Match(15); - State = 363; _localctx.By = Match(NUMBER); + State = 372; Match(15); + State = 373; _localctx.By = Match(NUMBER); } break; } @@ -2287,12 +2357,12 @@ public override TResult Accept(IParseTreeVisitor visitor) { [RuleVersion(0)] public AttributesContext attributes() { AttributesContext _localctx = new AttributesContext(_ctx, State); - EnterRule(_localctx, 54, RULE_attributes); + EnterRule(_localctx, 56, RULE_attributes); try { int _alt; EnterOuterAlt(_localctx, 1); { - State = 367; + State = 377; _errHandler.Sync(this); _alt = Interpreter.AdaptivePredict(_input,44,_ctx); do { @@ -2300,14 +2370,14 @@ public AttributesContext attributes() { case 1: { { - State = 366; attribute(); + State = 376; attribute(); } } break; default: throw new NoViableAltException(this); } - State = 369; + State = 379; _errHandler.Sync(this); _alt = Interpreter.AdaptivePredict(_input,44,_ctx); } while ( _alt!=2 && _alt!=-1 ); @@ -2356,33 +2426,33 @@ public override TResult Accept(IParseTreeVisitor visitor) { [RuleVersion(0)] public AttributeContext attribute() { AttributeContext _localctx = new AttributeContext(_ctx, State); - EnterRule(_localctx, 56, RULE_attribute); + EnterRule(_localctx, 58, RULE_attribute); int _la; try { EnterOuterAlt(_localctx, 1); { - State = 371; _localctx.Name = Match(ATTRIBUTE_ID); - State = 383; + State = 381; _localctx.Name = Match(ATTRIBUTE_ID); + State = 393; switch ( Interpreter.AdaptivePredict(_input,46,_ctx) ) { case 1: { - State = 372; Match(LEFT_PARENTHESIS); - State = 373; constant(); - State = 378; + State = 382; Match(LEFT_PARENTHESIS); + State = 383; constant(); + State = 388; _errHandler.Sync(this); _la = _input.La(1); while (_la==3) { { { - State = 374; Match(3); - State = 375; constant(); + State = 384; Match(3); + State = 385; constant(); } } - State = 380; + State = 390; _errHandler.Sync(this); _la = _input.La(1); } - State = 381; Match(RIGHT_PARENTHESIS); + State = 391; Match(RIGHT_PARENTHESIS); } break; } @@ -2435,164 +2505,168 @@ private bool expression_sempred(ExpressionContext _localctx, int predIndex) { } public static readonly string _serializedATN = - "\x3\xAF6F\x8320\x479D\xB75C\x4880\x1605\x191C\xAB37\x3.\x184\x4\x2\t\x2"+ + "\x3\xAF6F\x8320\x479D\xB75C\x4880\x1605\x191C\xAB37\x3/\x18E\x4\x2\t\x2"+ "\x4\x3\t\x3\x4\x4\t\x4\x4\x5\t\x5\x4\x6\t\x6\x4\a\t\a\x4\b\t\b\x4\t\t"+ "\t\x4\n\t\n\x4\v\t\v\x4\f\t\f\x4\r\t\r\x4\xE\t\xE\x4\xF\t\xF\x4\x10\t"+ "\x10\x4\x11\t\x11\x4\x12\t\x12\x4\x13\t\x13\x4\x14\t\x14\x4\x15\t\x15"+ "\x4\x16\t\x16\x4\x17\t\x17\x4\x18\t\x18\x4\x19\t\x19\x4\x1A\t\x1A\x4\x1B"+ - "\t\x1B\x4\x1C\t\x1C\x4\x1D\t\x1D\x4\x1E\t\x1E\x3\x2\x3\x2\x3\x3\x3\x3"+ - "\x3\x3\x6\x3\x42\n\x3\r\x3\xE\x3\x43\x3\x4\x3\x4\x5\x4H\n\x4\x3\x4\x3"+ - "\x4\x5\x4L\n\x4\x3\x4\x3\x4\x5\x4P\n\x4\x3\x4\x3\x4\x5\x4T\n\x4\x3\x4"+ - "\x3\x4\x5\x4X\n\x4\x3\x4\x3\x4\x5\x4\\\n\x4\x3\x4\x5\x4_\n\x4\x3\x5\x5"+ - "\x5\x62\n\x5\x3\x5\x3\x5\x3\x5\x3\x5\x5\x5h\n\x5\x3\x5\x3\x5\x3\x5\x3"+ - "\x6\x3\x6\x3\x6\x3\x6\x3\x6\x3\x6\x5\x6s\n\x6\x3\x6\x3\x6\x3\x6\x3\x6"+ + "\t\x1B\x4\x1C\t\x1C\x4\x1D\t\x1D\x4\x1E\t\x1E\x4\x1F\t\x1F\x3\x2\x3\x2"+ + "\x3\x3\x3\x3\x3\x3\x6\x3\x44\n\x3\r\x3\xE\x3\x45\x3\x4\x3\x4\x3\x4\x5"+ + "\x4K\n\x4\x3\x4\x3\x4\x5\x4O\n\x4\x3\x4\x3\x4\x5\x4S\n\x4\x3\x4\x3\x4"+ + "\x5\x4W\n\x4\x3\x4\x3\x4\x5\x4[\n\x4\x3\x4\x3\x4\x5\x4_\n\x4\x3\x4\x5"+ + "\x4\x62\n\x4\x3\x5\x5\x5\x65\n\x5\x3\x5\x3\x5\x3\x5\x3\x5\x5\x5k\n\x5"+ + "\x3\x5\x3\x5\x3\x5\x3\x6\x3\x6\x3\x6\x3\x6\x3\x6\x3\x6\x5\x6v\n\x6\x3"+ + "\x6\x3\x6\x3\x6\x3\x6\x3\x6\x3\x6\x3\x6\x3\x6\x3\x6\x3\x6\x3\x6\x3\x6"+ "\x3\x6\x3\x6\x3\x6\x3\x6\x3\x6\x3\x6\x3\x6\x3\x6\x3\x6\x3\x6\x3\x6\x3"+ "\x6\x3\x6\x3\x6\x3\x6\x3\x6\x3\x6\x3\x6\x3\x6\x3\x6\x3\x6\x3\x6\x3\x6"+ - "\x3\x6\x3\x6\x3\x6\x3\x6\x3\x6\x3\x6\x3\x6\x3\x6\x3\x6\a\x6\x99\n\x6\f"+ - "\x6\xE\x6\x9C\v\x6\x3\a\x3\a\x3\a\x5\a\xA1\n\a\x3\a\x3\a\x3\b\x3\b\x3"+ - "\b\x3\b\x5\b\xA9\n\b\x6\b\xAB\n\b\r\b\xE\b\xAC\x3\t\x5\t\xB0\n\t\x3\t"+ - "\x3\t\x5\t\xB4\n\t\x3\t\x3\t\x5\t\xB8\n\t\x3\t\x3\t\x5\t\xBC\n\t\x3\t"+ - "\x3\t\x5\t\xC0\n\t\x3\t\x3\t\x3\t\x3\t\x5\t\xC6\n\t\x3\n\x3\n\x5\n\xCA"+ - "\n\n\x3\n\x3\n\x5\n\xCE\n\n\x3\n\x3\n\x3\n\x5\n\xD3\n\n\x3\n\x3\n\x3\n"+ - "\x3\n\x3\n\x3\n\x5\n\xDB\n\n\x3\n\x3\n\x5\n\xDF\n\n\x3\n\x3\n\x3\n\x5"+ - "\n\xE4\n\n\x3\n\x3\n\x5\n\xE8\n\n\x3\v\x3\v\x3\v\x5\v\xED\n\v\x3\v\x3"+ - "\v\x3\v\x5\v\xF2\n\v\x3\v\x3\v\x3\v\x3\v\x3\v\x3\f\x3\f\x3\f\x3\f\x3\f"+ - "\x3\f\x3\r\x3\r\x3\r\x3\r\x3\xE\x3\xE\x3\xF\x3\xF\x3\xF\x3\xF\x5\xF\x109"+ - "\n\xF\x3\xF\x3\xF\x3\xF\x3\xF\x3\xF\x5\xF\x110\n\xF\x3\x10\x3\x10\x3\x10"+ - "\x3\x10\x3\x10\x3\x10\a\x10\x118\n\x10\f\x10\xE\x10\x11B\v\x10\x3\x10"+ - "\x3\x10\x5\x10\x11F\n\x10\x3\x10\x3\x10\x3\x11\x3\x11\x3\x11\x3\x11\x3"+ - "\x11\x3\x11\x3\x11\x3\x12\x3\x12\x3\x12\a\x12\x12D\n\x12\f\x12\xE\x12"+ - "\x130\v\x12\x3\x13\x5\x13\x133\n\x13\x3\x13\x3\x13\x3\x13\x3\x13\x3\x14"+ - "\x3\x14\x3\x14\a\x14\x13C\n\x14\f\x14\xE\x14\x13F\v\x14\x3\x15\x5\x15"+ - "\x142\n\x15\x3\x15\x3\x15\x3\x15\a\x15\x147\n\x15\f\x15\xE\x15\x14A\v"+ - "\x15\x3\x16\x3\x16\x3\x16\x3\x16\x3\x16\x5\x16\x151\n\x16\x3\x17\x3\x17"+ - "\x3\x18\x3\x18\x3\x19\x3\x19\x3\x1A\x3\x1A\x3\x1B\x3\x1B\x3\x1B\x3\x1B"+ - "\a\x1B\x15F\n\x1B\f\x1B\xE\x1B\x162\v\x1B\x3\x1B\x3\x1B\x3\x1B\x3\x1B"+ - "\x5\x1B\x168\n\x1B\x3\x1C\x3\x1C\x3\x1C\x3\x1C\x3\x1C\x5\x1C\x16F\n\x1C"+ - "\x3\x1D\x6\x1D\x172\n\x1D\r\x1D\xE\x1D\x173\x3\x1E\x3\x1E\x3\x1E\x3\x1E"+ - "\x3\x1E\a\x1E\x17B\n\x1E\f\x1E\xE\x1E\x17E\v\x1E\x3\x1E\x3\x1E\x5\x1E"+ - "\x182\n\x1E\x3\x1E\x2\x2\x3\n\x1F\x2\x2\x4\x2\x6\x2\b\x2\n\x2\f\x2\xE"+ - "\x2\x10\x2\x12\x2\x14\x2\x16\x2\x18\x2\x1A\x2\x1C\x2\x1E\x2 \x2\"\x2$"+ - "\x2&\x2(\x2*\x2,\x2.\x2\x30\x2\x32\x2\x34\x2\x36\x2\x38\x2:\x2\x2\x2\x1AC"+ - "\x2<\x3\x2\x2\x2\x4\x41\x3\x2\x2\x2\x6^\x3\x2\x2\x2\b\x61\x3\x2\x2\x2"+ - "\nr\x3\x2\x2\x2\f\x9D\x3\x2\x2\x2\xE\xA4\x3\x2\x2\x2\x10\xC5\x3\x2\x2"+ - "\x2\x12\xE7\x3\x2\x2\x2\x14\xE9\x3\x2\x2\x2\x16\xF8\x3\x2\x2\x2\x18\xFE"+ - "\x3\x2\x2\x2\x1A\x102\x3\x2\x2\x2\x1C\x104\x3\x2\x2\x2\x1E\x111\x3\x2"+ - "\x2\x2 \x122\x3\x2\x2\x2\"\x129\x3\x2\x2\x2$\x132\x3\x2\x2\x2&\x138\x3"+ - "\x2\x2\x2(\x141\x3\x2\x2\x2*\x150\x3\x2\x2\x2,\x152\x3\x2\x2\x2.\x154"+ - "\x3\x2\x2\x2\x30\x156\x3\x2\x2\x2\x32\x158\x3\x2\x2\x2\x34\x167\x3\x2"+ - "\x2\x2\x36\x169\x3\x2\x2\x2\x38\x171\x3\x2\x2\x2:\x175\x3\x2\x2\x2<=\x5"+ - "\x4\x3\x2=\x3\x3\x2\x2\x2>?\x5\x6\x4\x2?@\a\r\x2\x2@\x42\x3\x2\x2\x2\x41"+ - ">\x3\x2\x2\x2\x42\x43\x3\x2\x2\x2\x43\x41\x3\x2\x2\x2\x43\x44\x3\x2\x2"+ - "\x2\x44\x5\x3\x2\x2\x2\x45_\x5\n\x6\x2\x46H\x5\x38\x1D\x2G\x46\x3\x2\x2"+ - "\x2GH\x3\x2\x2\x2HI\x3\x2\x2\x2I_\x5\b\x5\x2JL\x5\x38\x1D\x2KJ\x3\x2\x2"+ - "\x2KL\x3\x2\x2\x2LM\x3\x2\x2\x2M_\x5\x1E\x10\x2NP\x5\x38\x1D\x2ON\x3\x2"+ - "\x2\x2OP\x3\x2\x2\x2PQ\x3\x2\x2\x2Q_\x5\x18\r\x2RT\x5\x38\x1D\x2SR\x3"+ - "\x2\x2\x2ST\x3\x2\x2\x2TU\x3\x2\x2\x2U_\x5\x14\v\x2VX\x5\x38\x1D\x2WV"+ - "\x3\x2\x2\x2WX\x3\x2\x2\x2XY\x3\x2\x2\x2Y_\x5\x16\f\x2Z\\\x5\x38\x1D\x2"+ - "[Z\x3\x2\x2\x2[\\\x3\x2\x2\x2\\]\x3\x2\x2\x2]_\x5\x1A\xE\x2^\x45\x3\x2"+ - "\x2\x2^G\x3\x2\x2\x2^K\x3\x2\x2\x2^O\x3\x2\x2\x2^S\x3\x2\x2\x2^W\x3\x2"+ - "\x2\x2^[\x3\x2\x2\x2_\a\x3\x2\x2\x2`\x62\x5\x38\x1D\x2\x61`\x3\x2\x2\x2"+ - "\x61\x62\x3\x2\x2\x2\x62\x63\x3\x2\x2\x2\x63\x64\a\x1E\x2\x2\x64g\a\'"+ - "\x2\x2\x65\x66\a\n\x2\x2\x66h\x5(\x15\x2g\x65\x3\x2\x2\x2gh\x3\x2\x2\x2"+ - "hi\x3\x2\x2\x2ij\a \x2\x2jk\x5\n\x6\x2k\t\x3\x2\x2\x2lm\b\x6\x1\x2mn\a"+ - "#\x2\x2ns\x5\n\x6\xFos\x5\x10\t\x2ps\x5\f\a\x2qs\x5\xE\b\x2rl\x3\x2\x2"+ - "\x2ro\x3\x2\x2\x2rp\x3\x2\x2\x2rq\x3\x2\x2\x2s\x9A\x3\x2\x2\x2tu\f\xE"+ - "\x2\x2uv\a\x6\x2\x2v\x99\x5\n\x6\xFwx\f\r\x2\x2xy\a\b\x2\x2y\x99\x5\n"+ - "\x6\xEz{\f\f\x2\x2{|\a\a\x2\x2|\x99\x5\n\x6\r}~\f\v\x2\x2~\x7F\a\x13\x2"+ - "\x2\x7F\x99\x5\n\x6\f\x80\x81\f\n\x2\x2\x81\x82\a\v\x2\x2\x82\x99\x5\n"+ - "\x6\v\x83\x84\f\t\x2\x2\x84\x85\a\x10\x2\x2\x85\x99\x5\n\x6\n\x86\x87"+ - "\f\b\x2\x2\x87\x88\a\xE\x2\x2\x88\x99\x5\n\x6\t\x89\x8A\f\a\x2\x2\x8A"+ - "\x8B\a\x14\x2\x2\x8B\x99\x5\n\x6\b\x8C\x8D\f\x6\x2\x2\x8D\x8E\a\x12\x2"+ - "\x2\x8E\x99\x5\n\x6\a\x8F\x90\f\x5\x2\x2\x90\x91\a\f\x2\x2\x91\x99\x5"+ - "\n\x6\x6\x92\x93\f\x4\x2\x2\x93\x94\a!\x2\x2\x94\x99\x5\n\x6\x5\x95\x96"+ - "\f\x3\x2\x2\x96\x97\a\"\x2\x2\x97\x99\x5\n\x6\x4\x98t\x3\x2\x2\x2\x98"+ - "w\x3\x2\x2\x2\x98z\x3\x2\x2\x2\x98}\x3\x2\x2\x2\x98\x80\x3\x2\x2\x2\x98"+ - "\x83\x3\x2\x2\x2\x98\x86\x3\x2\x2\x2\x98\x89\x3\x2\x2\x2\x98\x8C\x3\x2"+ - "\x2\x2\x98\x8F\x3\x2\x2\x2\x98\x92\x3\x2\x2\x2\x98\x95\x3\x2\x2\x2\x99"+ - "\x9C\x3\x2\x2\x2\x9A\x98\x3\x2\x2\x2\x9A\x9B\x3\x2\x2\x2\x9B\v\x3\x2\x2"+ - "\x2\x9C\x9A\x3\x2\x2\x2\x9D\x9E\x5\x10\t\x2\x9E\xA0\a%\x2\x2\x9F\xA1\x5"+ - "\n\x6\x2\xA0\x9F\x3\x2\x2\x2\xA0\xA1\x3\x2\x2\x2\xA1\xA2\x3\x2\x2\x2\xA2"+ - "\xA3\a&\x2\x2\xA3\r\x3\x2\x2\x2\xA4\xAA\x5\x10\t\x2\xA5\xA8\a\x4\x2\x2"+ - "\xA6\xA9\x5,\x17\x2\xA7\xA9\x5\f\a\x2\xA8\xA6\x3\x2\x2\x2\xA8\xA7\x3\x2"+ - "\x2\x2\xA9\xAB\x3\x2\x2\x2\xAA\xA5\x3\x2\x2\x2\xAB\xAC\x3\x2\x2\x2\xAC"+ - "\xAA\x3\x2\x2\x2\xAC\xAD\x3\x2\x2\x2\xAD\xF\x3\x2\x2\x2\xAE\xB0\x5\x38"+ - "\x1D\x2\xAF\xAE\x3\x2\x2\x2\xAF\xB0\x3\x2\x2\x2\xB0\xB1\x3\x2\x2\x2\xB1"+ - "\xC6\x5*\x16\x2\xB2\xB4\x5\x38\x1D\x2\xB3\xB2\x3\x2\x2\x2\xB3\xB4\x3\x2"+ - "\x2\x2\xB4\xB5\x3\x2\x2\x2\xB5\xC6\x5,\x17\x2\xB6\xB8\x5\x38\x1D\x2\xB7"+ - "\xB6\x3\x2\x2\x2\xB7\xB8\x3\x2\x2\x2\xB8\xB9\x3\x2\x2\x2\xB9\xC6\x5\x12"+ - "\n\x2\xBA\xBC\x5\x38\x1D\x2\xBB\xBA\x3\x2\x2\x2\xBB\xBC\x3\x2\x2\x2\xBC"+ - "\xBD\x3\x2\x2\x2\xBD\xC6\x5\x1C\xF\x2\xBE\xC0\x5\x38\x1D\x2\xBF\xBE\x3"+ - "\x2\x2\x2\xBF\xC0\x3\x2\x2\x2\xC0\xC1\x3\x2\x2\x2\xC1\xC2\a%\x2\x2\xC2"+ - "\xC3\x5\n\x6\x2\xC3\xC4\a&\x2\x2\xC4\xC6\x3\x2\x2\x2\xC5\xAF\x3\x2\x2"+ - "\x2\xC5\xB3\x3\x2\x2\x2\xC5\xB7\x3\x2\x2\x2\xC5\xBB\x3\x2\x2\x2\xC5\xBF"+ - "\x3\x2\x2\x2\xC6\x11\x3\x2\x2\x2\xC7\xC9\a\x15\x2\x2\xC8\xCA\x5,\x17\x2"+ - "\xC9\xC8\x3\x2\x2\x2\xC9\xCA\x3\x2\x2\x2\xCA\xCB\x3\x2\x2\x2\xCB\xCD\a"+ - "%\x2\x2\xCC\xCE\x5\"\x12\x2\xCD\xCC\x3\x2\x2\x2\xCD\xCE\x3\x2\x2\x2\xCE"+ - "\xCF\x3\x2\x2\x2\xCF\xD2\a&\x2\x2\xD0\xD1\a\n\x2\x2\xD1\xD3\x5(\x15\x2"+ - "\xD2\xD0\x3\x2\x2\x2\xD2\xD3\x3\x2\x2\x2\xD3\xD4\x3\x2\x2\x2\xD4\xD5\a"+ - "\x1A\x2\x2\xD5\xD6\x5\x4\x3\x2\xD6\xD7\a\x1B\x2\x2\xD7\xE8\x3\x2\x2\x2"+ - "\xD8\xDA\a\x15\x2\x2\xD9\xDB\x5,\x17\x2\xDA\xD9\x3\x2\x2\x2\xDA\xDB\x3"+ - "\x2\x2\x2\xDB\xDC\x3\x2\x2\x2\xDC\xDE\a%\x2\x2\xDD\xDF\x5\"\x12\x2\xDE"+ - "\xDD\x3\x2\x2\x2\xDE\xDF\x3\x2\x2\x2\xDF\xE0\x3\x2\x2\x2\xE0\xE3\a&\x2"+ - "\x2\xE1\xE2\a\n\x2\x2\xE2\xE4\x5(\x15\x2\xE3\xE1\x3\x2\x2\x2\xE3\xE4\x3"+ - "\x2\x2\x2\xE4\xE5\x3\x2\x2\x2\xE5\xE6\a \x2\x2\xE6\xE8\x5\n\x6\x2\xE7"+ - "\xC7\x3\x2\x2\x2\xE7\xD8\x3\x2\x2\x2\xE8\x13\x3\x2\x2\x2\xE9\xEA\a\x16"+ - "\x2\x2\xEA\xEC\a%\x2\x2\xEB\xED\a\x1E\x2\x2\xEC\xEB\x3\x2\x2\x2\xEC\xED"+ - "\x3\x2\x2\x2\xED\xEE\x3\x2\x2\x2\xEE\xF1\x5,\x17\x2\xEF\xF0\a\n\x2\x2"+ - "\xF0\xF2\x5(\x15\x2\xF1\xEF\x3\x2\x2\x2\xF1\xF2\x3\x2\x2\x2\xF2\xF3\x3"+ - "\x2\x2\x2\xF3\xF4\a\x1F\x2\x2\xF4\xF5\x5\n\x6\x2\xF5\xF6\a&\x2\x2\xF6"+ - "\xF7\x5\x6\x4\x2\xF7\x15\x3\x2\x2\x2\xF8\xF9\a\x17\x2\x2\xF9\xFA\a%\x2"+ - "\x2\xFA\xFB\x5\n\x6\x2\xFB\xFC\a&\x2\x2\xFC\xFD\x5\x6\x4\x2\xFD\x17\x3"+ - "\x2\x2\x2\xFE\xFF\a\x1A\x2\x2\xFF\x100\x5\x4\x3\x2\x100\x101\a\x1B\x2"+ - "\x2\x101\x19\x3\x2\x2\x2\x102\x103\a\x1D\x2\x2\x103\x1B\x3\x2\x2\x2\x104"+ - "\x105\x5,\x17\x2\x105\x106\a \x2\x2\x106\x10F\x5\n\x6\x2\x107\x109\x5"+ - "\x38\x1D\x2\x108\x107\x3\x2\x2\x2\x108\x109\x3\x2\x2\x2\x109\x10A\x3\x2"+ - "\x2\x2\x10A\x10B\a\x18\x2\x2\x10B\x10C\a%\x2\x2\x10C\x10D\x5\n\x6\x2\x10D"+ - "\x10E\a&\x2\x2\x10E\x110\x3\x2\x2\x2\x10F\x108\x3\x2\x2\x2\x10F\x110\x3"+ - "\x2\x2\x2\x110\x1D\x3\x2\x2\x2\x111\x112\a\x18\x2\x2\x112\x113\a%\x2\x2"+ - "\x113\x114\x5\n\x6\x2\x114\x115\a&\x2\x2\x115\x119\x5\x4\x3\x2\x116\x118"+ - "\x5 \x11\x2\x117\x116\x3\x2\x2\x2\x118\x11B\x3\x2\x2\x2\x119\x117\x3\x2"+ - "\x2\x2\x119\x11A\x3\x2\x2\x2\x11A\x11E\x3\x2\x2\x2\x11B\x119\x3\x2\x2"+ - "\x2\x11C\x11D\a\x19\x2\x2\x11D\x11F\x5\x4\x3\x2\x11E\x11C\x3\x2\x2\x2"+ - "\x11E\x11F\x3\x2\x2\x2\x11F\x120\x3\x2\x2\x2\x120\x121\a\x1B\x2\x2\x121"+ - "\x1F\x3\x2\x2\x2\x122\x123\a\x19\x2\x2\x123\x124\a\x18\x2\x2\x124\x125"+ - "\a%\x2\x2\x125\x126\x5\n\x6\x2\x126\x127\a&\x2\x2\x127\x128\x5\x4\x3\x2"+ - "\x128!\x3\x2\x2\x2\x129\x12E\x5$\x13\x2\x12A\x12B\a\x5\x2\x2\x12B\x12D"+ - "\x5$\x13\x2\x12C\x12A\x3\x2\x2\x2\x12D\x130\x3\x2\x2\x2\x12E\x12C\x3\x2"+ - "\x2\x2\x12E\x12F\x3\x2\x2\x2\x12F#\x3\x2\x2\x2\x130\x12E\x3\x2\x2\x2\x131"+ - "\x133\x5\x38\x1D\x2\x132\x131\x3\x2\x2\x2\x132\x133\x3\x2\x2\x2\x133\x134"+ - "\x3\x2\x2\x2\x134\x135\x5,\x17\x2\x135\x136\a\n\x2\x2\x136\x137\x5(\x15"+ - "\x2\x137%\x3\x2\x2\x2\x138\x13D\x5\n\x6\x2\x139\x13A\a\x5\x2\x2\x13A\x13C"+ - "\x5\n\x6\x2\x13B\x139\x3\x2\x2\x2\x13C\x13F\x3\x2\x2\x2\x13D\x13B\x3\x2"+ - "\x2\x2\x13D\x13E\x3\x2\x2\x2\x13E\'\x3\x2\x2\x2\x13F\x13D\x3\x2\x2\x2"+ - "\x140\x142\x5\x38\x1D\x2\x141\x140\x3\x2\x2\x2\x141\x142\x3\x2\x2\x2\x142"+ - "\x143\x3\x2\x2\x2\x143\x148\a\'\x2\x2\x144\x145\a\x4\x2\x2\x145\x147\a"+ - "\'\x2\x2\x146\x144\x3\x2\x2\x2\x147\x14A\x3\x2\x2\x2\x148\x146\x3\x2\x2"+ - "\x2\x148\x149\x3\x2\x2\x2\x149)\x3\x2\x2\x2\x14A\x148\x3\x2\x2\x2\x14B"+ - "\x151\x5.\x18\x2\x14C\x151\x5\x30\x19\x2\x14D\x151\x5\x32\x1A\x2\x14E"+ - "\x151\x5\x34\x1B\x2\x14F\x151\x5\x36\x1C\x2\x150\x14B\x3\x2\x2\x2\x150"+ - "\x14C\x3\x2\x2\x2\x150\x14D\x3\x2\x2\x2\x150\x14E\x3\x2\x2\x2\x150\x14F"+ - "\x3\x2\x2\x2\x151+\x3\x2\x2\x2\x152\x153\a\'\x2\x2\x153-\x3\x2\x2\x2\x154"+ - "\x155\a,\x2\x2\x155/\x3\x2\x2\x2\x156\x157\a+\x2\x2\x157\x31\x3\x2\x2"+ - "\x2\x158\x159\a\x1C\x2\x2\x159\x33\x3\x2\x2\x2\x15A\x15B\a\t\x2\x2\x15B"+ - "\x160\x5\n\x6\x2\x15C\x15D\a\x5\x2\x2\x15D\x15F\x5\n\x6\x2\x15E\x15C\x3"+ - "\x2\x2\x2\x15F\x162\x3\x2\x2\x2\x160\x15E\x3\x2\x2\x2\x160\x161\x3\x2"+ - "\x2\x2\x161\x163\x3\x2\x2\x2\x162\x160\x3\x2\x2\x2\x163\x164\a\x3\x2\x2"+ - "\x164\x168\x3\x2\x2\x2\x165\x166\a\t\x2\x2\x166\x168\a\x3\x2\x2\x167\x15A"+ - "\x3\x2\x2\x2\x167\x165\x3\x2\x2\x2\x168\x35\x3\x2\x2\x2\x169\x16A\a,\x2"+ - "\x2\x16A\x16B\a\xF\x2\x2\x16B\x16E\a,\x2\x2\x16C\x16D\a\x11\x2\x2\x16D"+ - "\x16F\a,\x2\x2\x16E\x16C\x3\x2\x2\x2\x16E\x16F\x3\x2\x2\x2\x16F\x37\x3"+ - "\x2\x2\x2\x170\x172\x5:\x1E\x2\x171\x170\x3\x2\x2\x2\x172\x173\x3\x2\x2"+ - "\x2\x173\x171\x3\x2\x2\x2\x173\x174\x3\x2\x2\x2\x174\x39\x3\x2\x2\x2\x175"+ - "\x181\a*\x2\x2\x176\x177\a%\x2\x2\x177\x17C\x5*\x16\x2\x178\x179\a\x5"+ - "\x2\x2\x179\x17B\x5*\x16\x2\x17A\x178\x3\x2\x2\x2\x17B\x17E\x3\x2\x2\x2"+ - "\x17C\x17A\x3\x2\x2\x2\x17C\x17D\x3\x2\x2\x2\x17D\x17F\x3\x2\x2\x2\x17E"+ - "\x17C\x3\x2\x2\x2\x17F\x180\a&\x2\x2\x180\x182\x3\x2\x2\x2\x181\x176\x3"+ - "\x2\x2\x2\x181\x182\x3\x2\x2\x2\x182;\x3\x2\x2\x2\x31\x43GKOSW[^\x61g"+ - "r\x98\x9A\xA0\xA8\xAC\xAF\xB3\xB7\xBB\xBF\xC5\xC9\xCD\xD2\xDA\xDE\xE3"+ - "\xE7\xEC\xF1\x108\x10F\x119\x11E\x12E\x132\x13D\x141\x148\x150\x160\x167"+ - "\x16E\x173\x17C\x181"; + "\x3\x6\a\x6\x9C\n\x6\f\x6\xE\x6\x9F\v\x6\x3\a\x3\a\x5\a\xA3\n\a\x3\a\x3"+ + "\a\x5\a\xA7\n\a\x3\a\x3\a\x3\b\x3\b\x5\b\xAD\n\b\x3\b\x3\b\x6\b\xB1\n"+ + "\b\r\b\xE\b\xB2\x3\t\x3\t\x5\t\xB7\n\t\x3\n\x5\n\xBA\n\n\x3\n\x3\n\x5"+ + "\n\xBE\n\n\x3\n\x3\n\x5\n\xC2\n\n\x3\n\x3\n\x5\n\xC6\n\n\x3\n\x3\n\x5"+ + "\n\xCA\n\n\x3\n\x3\n\x3\n\x3\n\x5\n\xD0\n\n\x3\v\x3\v\x5\v\xD4\n\v\x3"+ + "\v\x3\v\x5\v\xD8\n\v\x3\v\x3\v\x3\v\x3\v\x3\v\x3\v\x3\v\x3\v\x3\v\x3\v"+ + "\x5\v\xE4\n\v\x3\v\x3\v\x5\v\xE8\n\v\x3\v\x3\v\x3\v\x3\v\x3\v\x3\v\x3"+ + "\v\x5\v\xF1\n\v\x3\f\x3\f\x3\f\x5\f\xF6\n\f\x3\f\x3\f\x3\f\x5\f\xFB\n"+ + "\f\x3\f\x3\f\x3\f\x3\f\x3\f\x3\r\x3\r\x3\r\x3\r\x3\r\x3\r\x3\xE\x3\xE"+ + "\x3\xE\x3\xE\x3\xF\x3\xF\x3\x10\x3\x10\x3\x10\x3\x11\x3\x11\x3\x11\x3"+ + "\x11\x5\x11\x115\n\x11\x3\x11\x3\x11\x3\x11\x3\x11\x3\x11\x5\x11\x11C"+ + "\n\x11\x3\x12\x3\x12\x3\x12\x3\x12\x3\x12\x3\x12\a\x12\x124\n\x12\f\x12"+ + "\xE\x12\x127\v\x12\x3\x12\x3\x12\x5\x12\x12B\n\x12\x3\x12\x3\x12\x3\x13"+ + "\x3\x13\x3\x13\x3\x13\x3\x13\x3\x13\x3\x13\x3\x14\x3\x14\x3\x14\a\x14"+ + "\x139\n\x14\f\x14\xE\x14\x13C\v\x14\x3\x15\x5\x15\x13F\n\x15\x3\x15\x3"+ + "\x15\x3\x15\x3\x15\x3\x16\x3\x16\x3\x16\a\x16\x148\n\x16\f\x16\xE\x16"+ + "\x14B\v\x16\x3\x17\x5\x17\x14E\n\x17\x3\x17\x3\x17\x3\x17\a\x17\x153\n"+ + "\x17\f\x17\xE\x17\x156\v\x17\x3\x18\x3\x18\x3\x18\x3\x18\x3\x18\x5\x18"+ + "\x15D\n\x18\x3\x19\x3\x19\x3\x1A\x3\x1A\x3\x1B\x3\x1B\x3\x1C\x3\x1C\x3"+ + "\x1C\x3\x1C\a\x1C\x169\n\x1C\f\x1C\xE\x1C\x16C\v\x1C\x3\x1C\x3\x1C\x3"+ + "\x1C\x3\x1C\x5\x1C\x172\n\x1C\x3\x1D\x3\x1D\x3\x1D\x3\x1D\x3\x1D\x5\x1D"+ + "\x179\n\x1D\x3\x1E\x6\x1E\x17C\n\x1E\r\x1E\xE\x1E\x17D\x3\x1F\x3\x1F\x3"+ + "\x1F\x3\x1F\x3\x1F\a\x1F\x185\n\x1F\f\x1F\xE\x1F\x188\v\x1F\x3\x1F\x3"+ + "\x1F\x5\x1F\x18C\n\x1F\x3\x1F\x2\x2\x3\n \x2\x2\x4\x2\x6\x2\b\x2\n\x2"+ + "\f\x2\xE\x2\x10\x2\x12\x2\x14\x2\x16\x2\x18\x2\x1A\x2\x1C\x2\x1E\x2 \x2"+ + "\"\x2$\x2&\x2(\x2*\x2,\x2.\x2\x30\x2\x32\x2\x34\x2\x36\x2\x38\x2:\x2<"+ + "\x2\x2\x2\x1B6\x2>\x3\x2\x2\x2\x4\x43\x3\x2\x2\x2\x6\x61\x3\x2\x2\x2\b"+ + "\x64\x3\x2\x2\x2\nu\x3\x2\x2\x2\f\xA2\x3\x2\x2\x2\xE\xAC\x3\x2\x2\x2\x10"+ + "\xB6\x3\x2\x2\x2\x12\xCF\x3\x2\x2\x2\x14\xF0\x3\x2\x2\x2\x16\xF2\x3\x2"+ + "\x2\x2\x18\x101\x3\x2\x2\x2\x1A\x107\x3\x2\x2\x2\x1C\x10B\x3\x2\x2\x2"+ + "\x1E\x10D\x3\x2\x2\x2 \x110\x3\x2\x2\x2\"\x11D\x3\x2\x2\x2$\x12E\x3\x2"+ + "\x2\x2&\x135\x3\x2\x2\x2(\x13E\x3\x2\x2\x2*\x144\x3\x2\x2\x2,\x14D\x3"+ + "\x2\x2\x2.\x15C\x3\x2\x2\x2\x30\x15E\x3\x2\x2\x2\x32\x160\x3\x2\x2\x2"+ + "\x34\x162\x3\x2\x2\x2\x36\x171\x3\x2\x2\x2\x38\x173\x3\x2\x2\x2:\x17B"+ + "\x3\x2\x2\x2<\x17F\x3\x2\x2\x2>?\x5\x4\x3\x2?\x3\x3\x2\x2\x2@\x41\x5\x6"+ + "\x4\x2\x41\x42\a\r\x2\x2\x42\x44\x3\x2\x2\x2\x43@\x3\x2\x2\x2\x44\x45"+ + "\x3\x2\x2\x2\x45\x43\x3\x2\x2\x2\x45\x46\x3\x2\x2\x2\x46\x5\x3\x2\x2\x2"+ + "G\x62\x5\n\x6\x2H\x62\x5\x1E\x10\x2IK\x5:\x1E\x2JI\x3\x2\x2\x2JK\x3\x2"+ + "\x2\x2KL\x3\x2\x2\x2L\x62\x5\b\x5\x2MO\x5:\x1E\x2NM\x3\x2\x2\x2NO\x3\x2"+ + "\x2\x2OP\x3\x2\x2\x2P\x62\x5\"\x12\x2QS\x5:\x1E\x2RQ\x3\x2\x2\x2RS\x3"+ + "\x2\x2\x2ST\x3\x2\x2\x2T\x62\x5\x1A\xE\x2UW\x5:\x1E\x2VU\x3\x2\x2\x2V"+ + "W\x3\x2\x2\x2WX\x3\x2\x2\x2X\x62\x5\x16\f\x2Y[\x5:\x1E\x2ZY\x3\x2\x2\x2"+ + "Z[\x3\x2\x2\x2[\\\x3\x2\x2\x2\\\x62\x5\x18\r\x2]_\x5:\x1E\x2^]\x3\x2\x2"+ + "\x2^_\x3\x2\x2\x2_`\x3\x2\x2\x2`\x62\x5\x1C\xF\x2\x61G\x3\x2\x2\x2\x61"+ + "H\x3\x2\x2\x2\x61J\x3\x2\x2\x2\x61N\x3\x2\x2\x2\x61R\x3\x2\x2\x2\x61V"+ + "\x3\x2\x2\x2\x61Z\x3\x2\x2\x2\x61^\x3\x2\x2\x2\x62\a\x3\x2\x2\x2\x63\x65"+ + "\x5:\x1E\x2\x64\x63\x3\x2\x2\x2\x64\x65\x3\x2\x2\x2\x65\x66\x3\x2\x2\x2"+ + "\x66g\a\x1E\x2\x2gj\a(\x2\x2hi\a\n\x2\x2ik\x5,\x17\x2jh\x3\x2\x2\x2jk"+ + "\x3\x2\x2\x2kl\x3\x2\x2\x2lm\a \x2\x2mn\x5\n\x6\x2n\t\x3\x2\x2\x2op\b"+ + "\x6\x1\x2pq\a#\x2\x2qv\x5\n\x6\xFrv\x5\x12\n\x2sv\x5\f\a\x2tv\x5\xE\b"+ + "\x2uo\x3\x2\x2\x2ur\x3\x2\x2\x2us\x3\x2\x2\x2ut\x3\x2\x2\x2v\x9D\x3\x2"+ + "\x2\x2wx\f\xE\x2\x2xy\a\x6\x2\x2y\x9C\x5\n\x6\xFz{\f\r\x2\x2{|\a\b\x2"+ + "\x2|\x9C\x5\n\x6\xE}~\f\f\x2\x2~\x7F\a\a\x2\x2\x7F\x9C\x5\n\x6\r\x80\x81"+ + "\f\v\x2\x2\x81\x82\a\x13\x2\x2\x82\x9C\x5\n\x6\f\x83\x84\f\n\x2\x2\x84"+ + "\x85\a\v\x2\x2\x85\x9C\x5\n\x6\v\x86\x87\f\t\x2\x2\x87\x88\a\x10\x2\x2"+ + "\x88\x9C\x5\n\x6\n\x89\x8A\f\b\x2\x2\x8A\x8B\a\xE\x2\x2\x8B\x9C\x5\n\x6"+ + "\t\x8C\x8D\f\a\x2\x2\x8D\x8E\a\x14\x2\x2\x8E\x9C\x5\n\x6\b\x8F\x90\f\x6"+ + "\x2\x2\x90\x91\a\x12\x2\x2\x91\x9C\x5\n\x6\a\x92\x93\f\x5\x2\x2\x93\x94"+ + "\a\f\x2\x2\x94\x9C\x5\n\x6\x6\x95\x96\f\x4\x2\x2\x96\x97\a!\x2\x2\x97"+ + "\x9C\x5\n\x6\x5\x98\x99\f\x3\x2\x2\x99\x9A\a\"\x2\x2\x9A\x9C\x5\n\x6\x4"+ + "\x9Bw\x3\x2\x2\x2\x9Bz\x3\x2\x2\x2\x9B}\x3\x2\x2\x2\x9B\x80\x3\x2\x2\x2"+ + "\x9B\x83\x3\x2\x2\x2\x9B\x86\x3\x2\x2\x2\x9B\x89\x3\x2\x2\x2\x9B\x8C\x3"+ + "\x2\x2\x2\x9B\x8F\x3\x2\x2\x2\x9B\x92\x3\x2\x2\x2\x9B\x95\x3\x2\x2\x2"+ + "\x9B\x98\x3\x2\x2\x2\x9C\x9F\x3\x2\x2\x2\x9D\x9B\x3\x2\x2\x2\x9D\x9E\x3"+ + "\x2\x2\x2\x9E\v\x3\x2\x2\x2\x9F\x9D\x3\x2\x2\x2\xA0\xA3\a(\x2\x2\xA1\xA3"+ + "\x5\x14\v\x2\xA2\xA0\x3\x2\x2\x2\xA2\xA1\x3\x2\x2\x2\xA3\xA4\x3\x2\x2"+ + "\x2\xA4\xA6\a&\x2\x2\xA5\xA7\x5\n\x6\x2\xA6\xA5\x3\x2\x2\x2\xA6\xA7\x3"+ + "\x2\x2\x2\xA7\xA8\x3\x2\x2\x2\xA8\xA9\a\'\x2\x2\xA9\r\x3\x2\x2\x2\xAA"+ + "\xAD\x5\x12\n\x2\xAB\xAD\x5\f\a\x2\xAC\xAA\x3\x2\x2\x2\xAC\xAB\x3\x2\x2"+ + "\x2\xAD\xB0\x3\x2\x2\x2\xAE\xAF\a\x4\x2\x2\xAF\xB1\x5\x10\t\x2\xB0\xAE"+ + "\x3\x2\x2\x2\xB1\xB2\x3\x2\x2\x2\xB2\xB0\x3\x2\x2\x2\xB2\xB3\x3\x2\x2"+ + "\x2\xB3\xF\x3\x2\x2\x2\xB4\xB7\a(\x2\x2\xB5\xB7\x5\f\a\x2\xB6\xB4\x3\x2"+ + "\x2\x2\xB6\xB5\x3\x2\x2\x2\xB7\x11\x3\x2\x2\x2\xB8\xBA\x5:\x1E\x2\xB9"+ + "\xB8\x3\x2\x2\x2\xB9\xBA\x3\x2\x2\x2\xBA\xBB\x3\x2\x2\x2\xBB\xD0\x5.\x18"+ + "\x2\xBC\xBE\x5:\x1E\x2\xBD\xBC\x3\x2\x2\x2\xBD\xBE\x3\x2\x2\x2\xBE\xBF"+ + "\x3\x2\x2\x2\xBF\xD0\a(\x2\x2\xC0\xC2\x5:\x1E\x2\xC1\xC0\x3\x2\x2\x2\xC1"+ + "\xC2\x3\x2\x2\x2\xC2\xC3\x3\x2\x2\x2\xC3\xD0\x5\x14\v\x2\xC4\xC6\x5:\x1E"+ + "\x2\xC5\xC4\x3\x2\x2\x2\xC5\xC6\x3\x2\x2\x2\xC6\xC7\x3\x2\x2\x2\xC7\xD0"+ + "\x5 \x11\x2\xC8\xCA\x5:\x1E\x2\xC9\xC8\x3\x2\x2\x2\xC9\xCA\x3\x2\x2\x2"+ + "\xCA\xCB\x3\x2\x2\x2\xCB\xCC\a&\x2\x2\xCC\xCD\x5\n\x6\x2\xCD\xCE\a\'\x2"+ + "\x2\xCE\xD0\x3\x2\x2\x2\xCF\xB9\x3\x2\x2\x2\xCF\xBD\x3\x2\x2\x2\xCF\xC1"+ + "\x3\x2\x2\x2\xCF\xC5\x3\x2\x2\x2\xCF\xC9\x3\x2\x2\x2\xD0\x13\x3\x2\x2"+ + "\x2\xD1\xD3\a\x15\x2\x2\xD2\xD4\a(\x2\x2\xD3\xD2\x3\x2\x2\x2\xD3\xD4\x3"+ + "\x2\x2\x2\xD4\xD5\x3\x2\x2\x2\xD5\xD7\a&\x2\x2\xD6\xD8\x5&\x14\x2\xD7"+ + "\xD6\x3\x2\x2\x2\xD7\xD8\x3\x2\x2\x2\xD8\xD9\x3\x2\x2\x2\xD9\xDA\a\'\x2"+ + "\x2\xDA\xDB\a\n\x2\x2\xDB\xDC\x5,\x17\x2\xDC\xDD\x3\x2\x2\x2\xDD\xDE\a"+ + "\x1A\x2\x2\xDE\xDF\x5\x4\x3\x2\xDF\xE0\a\x1B\x2\x2\xE0\xF1\x3\x2\x2\x2"+ + "\xE1\xE3\a\x15\x2\x2\xE2\xE4\a(\x2\x2\xE3\xE2\x3\x2\x2\x2\xE3\xE4\x3\x2"+ + "\x2\x2\xE4\xE5\x3\x2\x2\x2\xE5\xE7\a&\x2\x2\xE6\xE8\x5&\x14\x2\xE7\xE6"+ + "\x3\x2\x2\x2\xE7\xE8\x3\x2\x2\x2\xE8\xE9\x3\x2\x2\x2\xE9\xEA\a\'\x2\x2"+ + "\xEA\xEB\a\n\x2\x2\xEB\xEC\x5,\x17\x2\xEC\xED\x3\x2\x2\x2\xED\xEE\a \x2"+ + "\x2\xEE\xEF\x5\n\x6\x2\xEF\xF1\x3\x2\x2\x2\xF0\xD1\x3\x2\x2\x2\xF0\xE1"+ + "\x3\x2\x2\x2\xF1\x15\x3\x2\x2\x2\xF2\xF3\a\x16\x2\x2\xF3\xF5\a&\x2\x2"+ + "\xF4\xF6\a\x1E\x2\x2\xF5\xF4\x3\x2\x2\x2\xF5\xF6\x3\x2\x2\x2\xF6\xF7\x3"+ + "\x2\x2\x2\xF7\xFA\a(\x2\x2\xF8\xF9\a\n\x2\x2\xF9\xFB\x5,\x17\x2\xFA\xF8"+ + "\x3\x2\x2\x2\xFA\xFB\x3\x2\x2\x2\xFB\xFC\x3\x2\x2\x2\xFC\xFD\a\x1F\x2"+ + "\x2\xFD\xFE\x5\n\x6\x2\xFE\xFF\a\'\x2\x2\xFF\x100\x5\x6\x4\x2\x100\x17"+ + "\x3\x2\x2\x2\x101\x102\a\x17\x2\x2\x102\x103\a&\x2\x2\x103\x104\x5\n\x6"+ + "\x2\x104\x105\a\'\x2\x2\x105\x106\x5\x6\x4\x2\x106\x19\x3\x2\x2\x2\x107"+ + "\x108\a\x1A\x2\x2\x108\x109\x5\x4\x3\x2\x109\x10A\a\x1B\x2\x2\x10A\x1B"+ + "\x3\x2\x2\x2\x10B\x10C\a\x1D\x2\x2\x10C\x1D\x3\x2\x2\x2\x10D\x10E\a%\x2"+ + "\x2\x10E\x10F\x5\n\x6\x2\x10F\x1F\x3\x2\x2\x2\x110\x111\a(\x2\x2\x111"+ + "\x112\a \x2\x2\x112\x11B\x5\n\x6\x2\x113\x115\x5:\x1E\x2\x114\x113\x3"+ + "\x2\x2\x2\x114\x115\x3\x2\x2\x2\x115\x116\x3\x2\x2\x2\x116\x117\a\x18"+ + "\x2\x2\x117\x118\a&\x2\x2\x118\x119\x5\n\x6\x2\x119\x11A\a\'\x2\x2\x11A"+ + "\x11C\x3\x2\x2\x2\x11B\x114\x3\x2\x2\x2\x11B\x11C\x3\x2\x2\x2\x11C!\x3"+ + "\x2\x2\x2\x11D\x11E\a\x18\x2\x2\x11E\x11F\a&\x2\x2\x11F\x120\x5\n\x6\x2"+ + "\x120\x121\a\'\x2\x2\x121\x125\x5\x4\x3\x2\x122\x124\x5$\x13\x2\x123\x122"+ + "\x3\x2\x2\x2\x124\x127\x3\x2\x2\x2\x125\x123\x3\x2\x2\x2\x125\x126\x3"+ + "\x2\x2\x2\x126\x12A\x3\x2\x2\x2\x127\x125\x3\x2\x2\x2\x128\x129\a\x19"+ + "\x2\x2\x129\x12B\x5\x4\x3\x2\x12A\x128\x3\x2\x2\x2\x12A\x12B\x3\x2\x2"+ + "\x2\x12B\x12C\x3\x2\x2\x2\x12C\x12D\a\x1B\x2\x2\x12D#\x3\x2\x2\x2\x12E"+ + "\x12F\a\x19\x2\x2\x12F\x130\a\x18\x2\x2\x130\x131\a&\x2\x2\x131\x132\x5"+ + "\n\x6\x2\x132\x133\a\'\x2\x2\x133\x134\x5\x4\x3\x2\x134%\x3\x2\x2\x2\x135"+ + "\x13A\x5(\x15\x2\x136\x137\a\x5\x2\x2\x137\x139\x5(\x15\x2\x138\x136\x3"+ + "\x2\x2\x2\x139\x13C\x3\x2\x2\x2\x13A\x138\x3\x2\x2\x2\x13A\x13B\x3\x2"+ + "\x2\x2\x13B\'\x3\x2\x2\x2\x13C\x13A\x3\x2\x2\x2\x13D\x13F\x5:\x1E\x2\x13E"+ + "\x13D\x3\x2\x2\x2\x13E\x13F\x3\x2\x2\x2\x13F\x140\x3\x2\x2\x2\x140\x141"+ + "\a(\x2\x2\x141\x142\a\n\x2\x2\x142\x143\x5,\x17\x2\x143)\x3\x2\x2\x2\x144"+ + "\x149\x5\n\x6\x2\x145\x146\a\x5\x2\x2\x146\x148\x5\n\x6\x2\x147\x145\x3"+ + "\x2\x2\x2\x148\x14B\x3\x2\x2\x2\x149\x147\x3\x2\x2\x2\x149\x14A\x3\x2"+ + "\x2\x2\x14A+\x3\x2\x2\x2\x14B\x149\x3\x2\x2\x2\x14C\x14E\x5:\x1E\x2\x14D"+ + "\x14C\x3\x2\x2\x2\x14D\x14E\x3\x2\x2\x2\x14E\x14F\x3\x2\x2\x2\x14F\x154"+ + "\a(\x2\x2\x150\x151\a\x4\x2\x2\x151\x153\a(\x2\x2\x152\x150\x3\x2\x2\x2"+ + "\x153\x156\x3\x2\x2\x2\x154\x152\x3\x2\x2\x2\x154\x155\x3\x2\x2\x2\x155"+ + "-\x3\x2\x2\x2\x156\x154\x3\x2\x2\x2\x157\x15D\x5\x30\x19\x2\x158\x15D"+ + "\x5\x32\x1A\x2\x159\x15D\x5\x34\x1B\x2\x15A\x15D\x5\x36\x1C\x2\x15B\x15D"+ + "\x5\x38\x1D\x2\x15C\x157\x3\x2\x2\x2\x15C\x158\x3\x2\x2\x2\x15C\x159\x3"+ + "\x2\x2\x2\x15C\x15A\x3\x2\x2\x2\x15C\x15B\x3\x2\x2\x2\x15D/\x3\x2\x2\x2"+ + "\x15E\x15F\a-\x2\x2\x15F\x31\x3\x2\x2\x2\x160\x161\a,\x2\x2\x161\x33\x3"+ + "\x2\x2\x2\x162\x163\a\x1C\x2\x2\x163\x35\x3\x2\x2\x2\x164\x165\a\t\x2"+ + "\x2\x165\x16A\x5\n\x6\x2\x166\x167\a\x5\x2\x2\x167\x169\x5\n\x6\x2\x168"+ + "\x166\x3\x2\x2\x2\x169\x16C\x3\x2\x2\x2\x16A\x168\x3\x2\x2\x2\x16A\x16B"+ + "\x3\x2\x2\x2\x16B\x16D\x3\x2\x2\x2\x16C\x16A\x3\x2\x2\x2\x16D\x16E\a\x3"+ + "\x2\x2\x16E\x172\x3\x2\x2\x2\x16F\x170\a\t\x2\x2\x170\x172\a\x3\x2\x2"+ + "\x171\x164\x3\x2\x2\x2\x171\x16F\x3\x2\x2\x2\x172\x37\x3\x2\x2\x2\x173"+ + "\x174\a-\x2\x2\x174\x175\a\xF\x2\x2\x175\x178\a-\x2\x2\x176\x177\a\x11"+ + "\x2\x2\x177\x179\a-\x2\x2\x178\x176\x3\x2\x2\x2\x178\x179\x3\x2\x2\x2"+ + "\x179\x39\x3\x2\x2\x2\x17A\x17C\x5<\x1F\x2\x17B\x17A\x3\x2\x2\x2\x17C"+ + "\x17D\x3\x2\x2\x2\x17D\x17B\x3\x2\x2\x2\x17D\x17E\x3\x2\x2\x2\x17E;\x3"+ + "\x2\x2\x2\x17F\x18B\a+\x2\x2\x180\x181\a&\x2\x2\x181\x186\x5.\x18\x2\x182"+ + "\x183\a\x5\x2\x2\x183\x185\x5.\x18\x2\x184\x182\x3\x2\x2\x2\x185\x188"+ + "\x3\x2\x2\x2\x186\x184\x3\x2\x2\x2\x186\x187\x3\x2\x2\x2\x187\x189\x3"+ + "\x2\x2\x2\x188\x186\x3\x2\x2\x2\x189\x18A\a\'\x2\x2\x18A\x18C\x3\x2\x2"+ + "\x2\x18B\x180\x3\x2\x2\x2\x18B\x18C\x3\x2\x2\x2\x18C=\x3\x2\x2\x2\x31"+ + "\x45JNRVZ^\x61\x64ju\x9B\x9D\xA2\xA6\xAC\xB2\xB6\xB9\xBD\xC1\xC5\xC9\xCF"+ + "\xD3\xD7\xE3\xE7\xF0\xF5\xFA\x114\x11B\x125\x12A\x13A\x13E\x149\x14D\x154"+ + "\x15C\x16A\x171\x178\x17D\x186\x18B"; public static readonly ATN _ATN = new ATNDeserializer().Deserialize(_serializedATN.ToCharArray()); } diff --git a/project/MetaCode/MetaCode.Compiler/obj/Debug/MetaCodeVisitor.cs b/project/MetaCode/MetaCode.Compiler/obj/Debug/MetaCodeVisitor.cs index c36efdc..b5108f3 100644 --- a/project/MetaCode/MetaCode.Compiler/obj/Debug/MetaCodeVisitor.cs +++ b/project/MetaCode/MetaCode.Compiler/obj/Debug/MetaCodeVisitor.cs @@ -8,7 +8,7 @@ // //------------------------------------------------------------------------------ -// Generated from E:\Development\Projects\C#\MetaCode\project\MetaCode\MetaCode.Compiler\Grammar\MetaCode.g4 by ANTLR 4.2-SNAPSHOT +// Generated from E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Compiler\Grammar\MetaCode.g4 by ANTLR 4.2-SNAPSHOT // Unreachable code detected #pragma warning disable 0162 @@ -44,6 +44,13 @@ public interface IMetaCodeVisitor : IParseTreeVisitor { /// The visitor result. Result VisitFormalParameter([NotNull] MetaCodeParser.FormalParameterContext context); + /// + /// Visit a parse tree produced by . + /// + /// The parse tree. + /// The visitor result. + Result VisitReturnStatement([NotNull] MetaCodeParser.ReturnStatementContext context); + /// /// Visit a parse tree produced by . /// @@ -135,6 +142,13 @@ public interface IMetaCodeVisitor : IParseTreeVisitor { /// The visitor result. Result VisitBooleanConstant([NotNull] MetaCodeParser.BooleanConstantContext context); + /// + /// Visit a parse tree produced by . + /// + /// The parse tree. + /// The visitor result. + Result VisitMemberTagExpression([NotNull] MetaCodeParser.MemberTagExpressionContext context); + /// /// Visit a parse tree produced by . /// @@ -219,13 +233,6 @@ public interface IMetaCodeVisitor : IParseTreeVisitor { /// The visitor result. Result VisitForeachStatement([NotNull] MetaCodeParser.ForeachStatementContext context); - /// - /// Visit a parse tree produced by . - /// - /// The parse tree. - /// The visitor result. - Result VisitIdentifier([NotNull] MetaCodeParser.IdentifierContext context); - /// /// Visit a parse tree produced by . /// diff --git a/project/MetaCode/MetaCode.Core/bin/Debug/MetaCode.Core.dll b/project/MetaCode/MetaCode.Core/bin/Debug/MetaCode.Core.dll index 1e0094a..97dba1b 100644 Binary files a/project/MetaCode/MetaCode.Core/bin/Debug/MetaCode.Core.dll and b/project/MetaCode/MetaCode.Core/bin/Debug/MetaCode.Core.dll differ diff --git a/project/MetaCode/MetaCode.Core/bin/Debug/MetaCode.Core.pdb b/project/MetaCode/MetaCode.Core/bin/Debug/MetaCode.Core.pdb index 5f99b8d..ebdaef2 100644 Binary files a/project/MetaCode/MetaCode.Core/bin/Debug/MetaCode.Core.pdb and b/project/MetaCode/MetaCode.Core/bin/Debug/MetaCode.Core.pdb differ diff --git a/project/MetaCode/MetaCode.Core/obj/Debug/DesignTimeResolveAssemblyReferencesInput.cache b/project/MetaCode/MetaCode.Core/obj/Debug/DesignTimeResolveAssemblyReferencesInput.cache index d634b8a..2a26b38 100644 Binary files a/project/MetaCode/MetaCode.Core/obj/Debug/DesignTimeResolveAssemblyReferencesInput.cache and b/project/MetaCode/MetaCode.Core/obj/Debug/DesignTimeResolveAssemblyReferencesInput.cache differ diff --git a/project/MetaCode/MetaCode.Core/obj/Debug/MetaCode.Core.csproj.FileListAbsolute.txt b/project/MetaCode/MetaCode.Core/obj/Debug/MetaCode.Core.csproj.FileListAbsolute.txt index bc7abe7..1df695f 100644 --- a/project/MetaCode/MetaCode.Core/obj/Debug/MetaCode.Core.csproj.FileListAbsolute.txt +++ b/project/MetaCode/MetaCode.Core/obj/Debug/MetaCode.Core.csproj.FileListAbsolute.txt @@ -1,9 +1,8 @@ -E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Core\bin\Debug\MetaCode.Core.dll -E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Core\bin\Debug\MetaCode.Core.pdb -E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Core\obj\Debug\MetaCode.Core.csprojResolveAssemblyReference.cache -E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Core\obj\Debug\MetaCode.Core.dll -E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Core\obj\Debug\MetaCode.Core.pdb E:\Development\Projects\C#\MetaCode\project\MetaCode\MetaCode.Core\bin\Debug\MetaCode.Core.dll E:\Development\Projects\C#\MetaCode\project\MetaCode\MetaCode.Core\bin\Debug\MetaCode.Core.pdb E:\Development\Projects\C#\MetaCode\project\MetaCode\MetaCode.Core\obj\Debug\MetaCode.Core.dll E:\Development\Projects\C#\MetaCode\project\MetaCode\MetaCode.Core\obj\Debug\MetaCode.Core.pdb +E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Core\bin\Debug\MetaCode.Core.dll +E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Core\bin\Debug\MetaCode.Core.pdb +E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Core\obj\Debug\MetaCode.Core.dll +E:\Development\Projects\MetaCode\project\MetaCode\MetaCode.Core\obj\Debug\MetaCode.Core.pdb diff --git a/project/MetaCode/MetaCode.Core/obj/Debug/MetaCode.Core.dll b/project/MetaCode/MetaCode.Core/obj/Debug/MetaCode.Core.dll index 1e0094a..97dba1b 100644 Binary files a/project/MetaCode/MetaCode.Core/obj/Debug/MetaCode.Core.dll and b/project/MetaCode/MetaCode.Core/obj/Debug/MetaCode.Core.dll differ diff --git a/project/MetaCode/MetaCode.Core/obj/Debug/MetaCode.Core.pdb b/project/MetaCode/MetaCode.Core/obj/Debug/MetaCode.Core.pdb index 5f99b8d..ebdaef2 100644 Binary files a/project/MetaCode/MetaCode.Core/obj/Debug/MetaCode.Core.pdb and b/project/MetaCode/MetaCode.Core/obj/Debug/MetaCode.Core.pdb differ diff --git a/project/MetaCode/MetaCode.sln b/project/MetaCode/MetaCode.sln index 59855ab..3a70fde 100644 --- a/project/MetaCode/MetaCode.sln +++ b/project/MetaCode/MetaCode.sln @@ -9,6 +9,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MetaCode.Compiler.Tests", " EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MetaCode.Core", "MetaCode.Core\MetaCode.Core.csproj", "{838E8EE6-AAC1-4B4F-899A-F1978AFCF2E3}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MetaCode.CodeVisualizer", "MetaCode.CodeVisualizer\MetaCode.CodeVisualizer.csproj", "{8FA5FC15-2A2D-4DCD-A8CD-8664B0EFCBAA}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -27,6 +29,10 @@ Global {838E8EE6-AAC1-4B4F-899A-F1978AFCF2E3}.Debug|Any CPU.Build.0 = Debug|Any CPU {838E8EE6-AAC1-4B4F-899A-F1978AFCF2E3}.Release|Any CPU.ActiveCfg = Release|Any CPU {838E8EE6-AAC1-4B4F-899A-F1978AFCF2E3}.Release|Any CPU.Build.0 = Release|Any CPU + {8FA5FC15-2A2D-4DCD-A8CD-8664B0EFCBAA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8FA5FC15-2A2D-4DCD-A8CD-8664B0EFCBAA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8FA5FC15-2A2D-4DCD-A8CD-8664B0EFCBAA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8FA5FC15-2A2D-4DCD-A8CD-8664B0EFCBAA}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/project/MetaCode/MetaCode.sln.DotSettings.user b/project/MetaCode/MetaCode.sln.DotSettings.user index e0dfb8d..dd85843 100644 --- a/project/MetaCode/MetaCode.sln.DotSettings.user +++ b/project/MetaCode/MetaCode.sln.DotSettings.user @@ -1,7 +1,7 @@  True MyVisitorTest - <Session><Elements><UnitTestElement Provider="nUnit" Id="MetaCode.Compiler.Tests.AbstractTreeVisitorTestFixture" type="NUnitTestFixtureElement" Project="FCAF7D34-A84D-4870-A77B-9BB8EF342B20" /><UnitTestElement Provider="nUnit" Id="MetaCode.Compiler.Tests.MyVisitorCompilerTestFixture" type="NUnitTestFixtureElement" Project="FCAF7D34-A84D-4870-A77B-9BB8EF342B20" /><UnitTestElement Provider="nUnit" Id="MetaCode.Compiler.Tests.TypeHelperTestFixture" type="NUnitTestFixtureElement" Project="FCAF7D34-A84D-4870-A77B-9BB8EF342B20" /><UnitTestElement Provider="nUnit" Id="MetaCode.Compiler.Tests.AbstractTreeVisitorTestFixture.AbstractTreeVisitorDeclareVariableTest" ParentId="MetaCode.Compiler.Tests.AbstractTreeVisitorTestFixture" type="NUnitTestElement" TypeName="MetaCode.Compiler.Tests.AbstractTreeVisitorTestFixture" MethodName="AbstractTreeVisitorDeclareVariableTest" Project="FCAF7D34-A84D-4870-A77B-9BB8EF342B20" /><UnitTestElement Provider="nUnit" Id="MetaCode.Compiler.Tests.AbstractTreeVisitorTestFixture.AbstractTreeVisitorVisitConstantShouldParseStringTest" ParentId="MetaCode.Compiler.Tests.AbstractTreeVisitorTestFixture" type="NUnitTestElement" TypeName="MetaCode.Compiler.Tests.AbstractTreeVisitorTestFixture" MethodName="AbstractTreeVisitorVisitConstantShouldParseStringTest" Project="FCAF7D34-A84D-4870-A77B-9BB8EF342B20" /><UnitTestElement Provider="nUnit" Id="MetaCode.Compiler.Tests.AbstractTreeVisitorTestFixture.AbstractTreeVisitorVisitStatementsTest" ParentId="MetaCode.Compiler.Tests.AbstractTreeVisitorTestFixture" type="NUnitTestElement" TypeName="MetaCode.Compiler.Tests.AbstractTreeVisitorTestFixture" MethodName="AbstractTreeVisitorVisitStatementsTest" Project="FCAF7D34-A84D-4870-A77B-9BB8EF342B20" /><UnitTestElement Provider="nUnit" Id="MetaCode.Compiler.Tests.AbstractTreeVisitorTestFixture.AbstractTreeVisitorVisitWhileTest" ParentId="MetaCode.Compiler.Tests.AbstractTreeVisitorTestFixture" type="NUnitTestElement" TypeName="MetaCode.Compiler.Tests.AbstractTreeVisitorTestFixture" MethodName="AbstractTreeVisitorVisitWhileTest" Project="FCAF7D34-A84D-4870-A77B-9BB8EF342B20" /><UnitTestElement Provider="nUnit" Id="MetaCode.Compiler.Tests.MyVisitorCompilerTestFixture.MyVisitorTest" ParentId="MetaCode.Compiler.Tests.MyVisitorCompilerTestFixture" type="NUnitTestElement" TypeName="MetaCode.Compiler.Tests.MyVisitorCompilerTestFixture" MethodName="MyVisitorTest" Project="FCAF7D34-A84D-4870-A77B-9BB8EF342B20" /><UnitTestElement Provider="nUnit" Id="MetaCode.Compiler.Tests.TypeHelperTestFixture.TypeHelperIsEnumerableShouldReturnTrueTest" ParentId="MetaCode.Compiler.Tests.TypeHelperTestFixture" type="NUnitTestElement" TypeName="MetaCode.Compiler.Tests.TypeHelperTestFixture" MethodName="TypeHelperIsEnumerableShouldReturnTrueTest" Project="FCAF7D34-A84D-4870-A77B-9BB8EF342B20" /></Elements></Session> + <Session><Elements><UnitTestElement Provider="nUnit" Id="MetaCode.Compiler.Tests.AbstractTreeVisitorTestFixture" type="NUnitTestFixtureElement" Project="FCAF7D34-A84D-4870-A77B-9BB8EF342B20" /><UnitTestElement Provider="nUnit" Id="MetaCode.Compiler.Tests.MyVisitorCompilerTestFixture" type="NUnitTestFixtureElement" Project="FCAF7D34-A84D-4870-A77B-9BB8EF342B20" /><UnitTestElement Provider="nUnit" Id="MetaCode.Compiler.Tests.TypeHelperTestFixture" type="NUnitTestFixtureElement" Project="FCAF7D34-A84D-4870-A77B-9BB8EF342B20" /><UnitTestElement Provider="nUnit" Id="MetaCode.Compiler.Tests.AbstractTreeVisitorTestFixture.AbstractTreeVisitorDeclareFunctionTest" ParentId="MetaCode.Compiler.Tests.AbstractTreeVisitorTestFixture" type="NUnitTestElement" TypeName="MetaCode.Compiler.Tests.AbstractTreeVisitorTestFixture" MethodName="AbstractTreeVisitorDeclareFunctionTest" Project="FCAF7D34-A84D-4870-A77B-9BB8EF342B20" /><UnitTestElement Provider="nUnit" Id="MetaCode.Compiler.Tests.AbstractTreeVisitorTestFixture.AbstractTreeVisitorDeclareVariableTest" ParentId="MetaCode.Compiler.Tests.AbstractTreeVisitorTestFixture" type="NUnitTestElement" TypeName="MetaCode.Compiler.Tests.AbstractTreeVisitorTestFixture" MethodName="AbstractTreeVisitorDeclareVariableTest" Project="FCAF7D34-A84D-4870-A77B-9BB8EF342B20" /><UnitTestElement Provider="nUnit" Id="MetaCode.Compiler.Tests.AbstractTreeVisitorTestFixture.AbstractTreeVisitorVisitConstantShouldParseStringTest" ParentId="MetaCode.Compiler.Tests.AbstractTreeVisitorTestFixture" type="NUnitTestElement" TypeName="MetaCode.Compiler.Tests.AbstractTreeVisitorTestFixture" MethodName="AbstractTreeVisitorVisitConstantShouldParseStringTest" Project="FCAF7D34-A84D-4870-A77B-9BB8EF342B20" /><UnitTestElement Provider="nUnit" Id="MetaCode.Compiler.Tests.AbstractTreeVisitorTestFixture.AbstractTreeVisitorVisitStatementsTest" ParentId="MetaCode.Compiler.Tests.AbstractTreeVisitorTestFixture" type="NUnitTestElement" TypeName="MetaCode.Compiler.Tests.AbstractTreeVisitorTestFixture" MethodName="AbstractTreeVisitorVisitStatementsTest" Project="FCAF7D34-A84D-4870-A77B-9BB8EF342B20" /><UnitTestElement Provider="nUnit" Id="MetaCode.Compiler.Tests.AbstractTreeVisitorTestFixture.AbstractTreeVisitorVisitWhileTest" ParentId="MetaCode.Compiler.Tests.AbstractTreeVisitorTestFixture" type="NUnitTestElement" TypeName="MetaCode.Compiler.Tests.AbstractTreeVisitorTestFixture" MethodName="AbstractTreeVisitorVisitWhileTest" Project="FCAF7D34-A84D-4870-A77B-9BB8EF342B20" /><UnitTestElement Provider="nUnit" Id="MetaCode.Compiler.Tests.MyVisitorCompilerTestFixture.MyVisitorTest" ParentId="MetaCode.Compiler.Tests.MyVisitorCompilerTestFixture" type="NUnitTestElement" TypeName="MetaCode.Compiler.Tests.MyVisitorCompilerTestFixture" MethodName="MyVisitorTest" Project="FCAF7D34-A84D-4870-A77B-9BB8EF342B20" /><UnitTestElement Provider="nUnit" Id="MetaCode.Compiler.Tests.TypeHelperTestFixture.TypeHelperIsEnumerableShouldReturnTrueTest" ParentId="MetaCode.Compiler.Tests.TypeHelperTestFixture" type="NUnitTestElement" TypeName="MetaCode.Compiler.Tests.TypeHelperTestFixture" MethodName="TypeHelperIsEnumerableShouldReturnTrueTest" Project="FCAF7D34-A84D-4870-A77B-9BB8EF342B20" /></Elements></Session> True AbstractTreeVisitorVisitWhileTest - <Session><Elements><UnitTestElement Provider="nUnit" Id="MetaCode.Compiler.Tests.AbstractTreeVisitorTestFixture" type="NUnitTestFixtureElement" Project="FCAF7D34-A84D-4870-A77B-9BB8EF342B20" /><UnitTestElement Provider="nUnit" Id="MetaCode.Compiler.Tests.AbstractTreeVisitorTestFixture.AbstractTreeVisitorDeclareVariableTest" ParentId="MetaCode.Compiler.Tests.AbstractTreeVisitorTestFixture" type="NUnitTestElement" TypeName="MetaCode.Compiler.Tests.AbstractTreeVisitorTestFixture" MethodName="AbstractTreeVisitorDeclareVariableTest" Project="FCAF7D34-A84D-4870-A77B-9BB8EF342B20" /><UnitTestElement Provider="nUnit" Id="MetaCode.Compiler.Tests.AbstractTreeVisitorTestFixture.AbstractTreeVisitorVisitWhileTest" ParentId="MetaCode.Compiler.Tests.AbstractTreeVisitorTestFixture" type="NUnitTestElement" TypeName="MetaCode.Compiler.Tests.AbstractTreeVisitorTestFixture" MethodName="AbstractTreeVisitorVisitWhileTest" Project="FCAF7D34-A84D-4870-A77B-9BB8EF342B20" /></Elements></Session> \ No newline at end of file + <Session><Elements><UnitTestElement Provider="nUnit" Id="MetaCode.Compiler.Tests.AbstractTreeVisitorTestFixture" type="NUnitTestFixtureElement" Project="FCAF7D34-A84D-4870-A77B-9BB8EF342B20" /><UnitTestElement Provider="nUnit" Id="MetaCode.Compiler.Tests.CustomVisitorTestFixture" type="NUnitTestFixtureElement" Project="FCAF7D34-A84D-4870-A77B-9BB8EF342B20" /><UnitTestElement Provider="nUnit" Id="MetaCode.Compiler.Tests.AbstractTreeVisitorTestFixture.AbstractTreeVisitorDeclareFunctionTest" ParentId="MetaCode.Compiler.Tests.AbstractTreeVisitorTestFixture" type="NUnitTestElement" TypeName="MetaCode.Compiler.Tests.AbstractTreeVisitorTestFixture" MethodName="AbstractTreeVisitorDeclareFunctionTest" Project="FCAF7D34-A84D-4870-A77B-9BB8EF342B20" /><UnitTestElement Provider="nUnit" Id="MetaCode.Compiler.Tests.AbstractTreeVisitorTestFixture.AbstractTreeVisitorDeclareVariableTest" ParentId="MetaCode.Compiler.Tests.AbstractTreeVisitorTestFixture" type="NUnitTestElement" TypeName="MetaCode.Compiler.Tests.AbstractTreeVisitorTestFixture" MethodName="AbstractTreeVisitorDeclareVariableTest" Project="FCAF7D34-A84D-4870-A77B-9BB8EF342B20" /><UnitTestElement Provider="nUnit" Id="MetaCode.Compiler.Tests.AbstractTreeVisitorTestFixture.AbstractTreeVisitorVisitWhileTest" ParentId="MetaCode.Compiler.Tests.AbstractTreeVisitorTestFixture" type="NUnitTestElement" TypeName="MetaCode.Compiler.Tests.AbstractTreeVisitorTestFixture" MethodName="AbstractTreeVisitorVisitWhileTest" Project="FCAF7D34-A84D-4870-A77B-9BB8EF342B20" /><UnitTestElement Provider="nUnit" Id="MetaCode.Compiler.Tests.CustomVisitorTestFixture.CustomVisitorGraphConverterTest" ParentId="MetaCode.Compiler.Tests.CustomVisitorTestFixture" type="NUnitTestElement" TypeName="MetaCode.Compiler.Tests.CustomVisitorTestFixture" MethodName="CustomVisitorGraphConverterTest" Project="FCAF7D34-A84D-4870-A77B-9BB8EF342B20" /><UnitTestElement Provider="nUnit" Id="MetaCode.Compiler.Tests.CustomVisitorTestFixture.CustomVisitorTest" ParentId="MetaCode.Compiler.Tests.CustomVisitorTestFixture" type="NUnitTestElement" TypeName="MetaCode.Compiler.Tests.CustomVisitorTestFixture" MethodName="CustomVisitorTest" Project="FCAF7D34-A84D-4870-A77B-9BB8EF342B20" /></Elements></Session> \ No newline at end of file diff --git a/project/MetaCode/MetaCode.v12.suo b/project/MetaCode/MetaCode.v12.suo index 0182bdb..e11d7fb 100644 Binary files a/project/MetaCode/MetaCode.v12.suo and b/project/MetaCode/MetaCode.v12.suo differ diff --git a/web/dist/img/graph/acceptDeleteIcon.png b/web/dist/img/graph/acceptDeleteIcon.png new file mode 100644 index 0000000..02a0628 Binary files /dev/null and b/web/dist/img/graph/acceptDeleteIcon.png differ diff --git a/web/dist/img/graph/addNodeIcon.png b/web/dist/img/graph/addNodeIcon.png new file mode 100644 index 0000000..6fa3061 Binary files /dev/null and b/web/dist/img/graph/addNodeIcon.png differ diff --git a/web/dist/img/graph/backIcon.png b/web/dist/img/graph/backIcon.png new file mode 100644 index 0000000..e2f9912 Binary files /dev/null and b/web/dist/img/graph/backIcon.png differ diff --git a/web/dist/img/graph/connectIcon.png b/web/dist/img/graph/connectIcon.png new file mode 100644 index 0000000..4164da1 Binary files /dev/null and b/web/dist/img/graph/connectIcon.png differ diff --git a/web/dist/img/graph/cross.png b/web/dist/img/graph/cross.png new file mode 100644 index 0000000..9cbd189 Binary files /dev/null and b/web/dist/img/graph/cross.png differ diff --git a/web/dist/img/graph/cross2.png b/web/dist/img/graph/cross2.png new file mode 100644 index 0000000..9fc4b95 Binary files /dev/null and b/web/dist/img/graph/cross2.png differ diff --git a/web/dist/img/graph/deleteIcon.png b/web/dist/img/graph/deleteIcon.png new file mode 100644 index 0000000..5402564 Binary files /dev/null and b/web/dist/img/graph/deleteIcon.png differ diff --git a/web/dist/img/graph/downArrow.png b/web/dist/img/graph/downArrow.png new file mode 100644 index 0000000..e77d5e6 Binary files /dev/null and b/web/dist/img/graph/downArrow.png differ diff --git a/web/dist/img/graph/editIcon.png b/web/dist/img/graph/editIcon.png new file mode 100644 index 0000000..494d0f0 Binary files /dev/null and b/web/dist/img/graph/editIcon.png differ diff --git a/web/dist/img/graph/leftArrow.png b/web/dist/img/graph/leftArrow.png new file mode 100644 index 0000000..3823536 Binary files /dev/null and b/web/dist/img/graph/leftArrow.png differ diff --git a/web/dist/img/graph/minus.png b/web/dist/img/graph/minus.png new file mode 100644 index 0000000..3069807 Binary files /dev/null and b/web/dist/img/graph/minus.png differ diff --git a/web/dist/img/graph/plus.png b/web/dist/img/graph/plus.png new file mode 100644 index 0000000..f7ab2a3 Binary files /dev/null and b/web/dist/img/graph/plus.png differ diff --git a/web/dist/img/graph/rightArrow.png b/web/dist/img/graph/rightArrow.png new file mode 100644 index 0000000..c3a209d Binary files /dev/null and b/web/dist/img/graph/rightArrow.png differ diff --git a/web/dist/img/graph/upArrow.png b/web/dist/img/graph/upArrow.png new file mode 100644 index 0000000..8aedced Binary files /dev/null and b/web/dist/img/graph/upArrow.png differ diff --git a/web/dist/img/graph/zoomExtends.png b/web/dist/img/graph/zoomExtends.png new file mode 100644 index 0000000..74595c6 Binary files /dev/null and b/web/dist/img/graph/zoomExtends.png differ diff --git a/web/dist/img/timeline/delete.png b/web/dist/img/timeline/delete.png new file mode 100644 index 0000000..d54d0e0 Binary files /dev/null and b/web/dist/img/timeline/delete.png differ diff --git a/web/dist/vis.css b/web/dist/vis.css new file mode 100644 index 0000000..e290c0a --- /dev/null +++ b/web/dist/vis.css @@ -0,0 +1,468 @@ +.vis.timeline { +} + + +.vis.timeline.rootpanel { + position: relative; + overflow: hidden; + + border: 1px solid #bfbfbf; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +.vis.timeline .vpanel { + position: absolute; + overflow: hidden; +} + + +.vis.timeline .groupset { + position: absolute; + padding: 0; + margin: 0; +} + +.vis.timeline .labels { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + + padding: 0; + margin: 0; + + border-right: 1px solid #bfbfbf; + box-sizing: border-box; + -moz-box-sizing: border-box; +} + +.vis.timeline .labels .label-set { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + + overflow: hidden; + + border-top: none; + border-bottom: 1px solid #bfbfbf; +} + +.vis.timeline .labels .label-set .vlabel { + position: absolute; + left: 0; + top: 0; + width: 100%; + color: #4d4d4d; +} + +.vis.timeline.top .labels .label-set .vlabel, +.vis.timeline.top .groupset .itemset-axis { + border-top: 1px solid #bfbfbf; + border-bottom: none; +} + +.vis.timeline.bottom .labels .label-set .vlabel, +.vis.timeline.bottom .groupset .itemset-axis { + border-top: none; + border-bottom: 1px solid #bfbfbf; +} + +.vis.timeline .labels .label-set .vlabel .inner { + display: inline-block; + padding: 5px; +} + + +.vis.timeline .itemset { + position: absolute; + padding: 0; + margin: 0; + overflow: hidden; +} + +.vis.timeline .background { +} + +.vis.timeline .foreground { +} + +.vis.timeline .itemset-axis { + position: absolute; +} + + +.vis.timeline .item { + position: absolute; + color: #1A1A1A; + border-color: #97B0F8; + background-color: #D5DDF6; + display: inline-block; + padding: 5px; +} + +.vis.timeline .item.selected { + border-color: #FFC200; + background-color: #FFF785; + z-index: 999; +} + +.vis.timeline.editable .item.selected { + cursor: move; +} + +.vis.timeline .item.point.selected { + background-color: #FFF785; + z-index: 999; +} +.vis.timeline .item.point.selected .dot { + border-color: #FFC200; +} + +.vis.timeline .item.cluster { + /* TODO: use another color or pattern? */ + background: #97B0F8 url('img/cluster_bg.png'); + color: white; +} +.vis.timeline .item.cluster.point { + border-color: #D5DDF6; +} + +.vis.timeline .item.box { + text-align: center; + border-style: solid; + border-width: 1px; + border-radius: 5px; + -moz-border-radius: 5px; /* For Firefox 3.6 and older */ +} + +.vis.timeline .item.point { + background: none; +} + +.vis.timeline .dot, +.vis.timeline .item.dot { + padding: 0; + border: 5px solid #97B0F8; + position: absolute; + border-radius: 5px; + -moz-border-radius: 5px; /* For Firefox 3.6 and older */ +} + +.vis.timeline .item.range, +.vis.timeline .item.rangeoverflow{ + border-style: solid; + border-width: 1px; + border-radius: 2px; + -moz-border-radius: 2px; /* For Firefox 3.6 and older */ + box-sizing: border-box; +} + +.vis.timeline .item.range .content, +.vis.timeline .item.rangeoverflow .content { + position: relative; + display: inline-block; +} + +.vis.timeline .item.range .content { + overflow: hidden; + max-width: 100%; +} + +.vis.timeline .item.line { + padding: 0; + position: absolute; + width: 0; + border-left-width: 1px; + border-left-style: solid; +} + +.vis.timeline .item .content { + white-space: nowrap; + overflow: hidden; +} + +.vis.timeline .item .delete { + background: url('img/timeline/delete.png') no-repeat top center; + position: absolute; + width: 24px; + height: 24px; + top: 0; + right: -24px; + cursor: pointer; +} + +.vis.timeline .item.range .drag-left, +.vis.timeline .item.rangeoverflow .drag-left { + position: absolute; + width: 24px; + height: 100%; + top: 0; + left: -4px; + + cursor: w-resize; + z-index: 10000; +} + +.vis.timeline .item.range .drag-right, +.vis.timeline .item.rangeoverflow .drag-right { + position: absolute; + width: 24px; + height: 100%; + top: 0; + right: -4px; + + cursor: e-resize; + z-index: 10001; /* a little higher z-index than .drag-left */ +} + +.vis.timeline .axis { + position: relative; +} + +.vis.timeline .axis .text { + position: absolute; + color: #4d4d4d; + padding: 3px; + white-space: nowrap; +} + +.vis.timeline .axis .text.measure { + position: absolute; + padding-left: 0; + padding-right: 0; + margin-left: 0; + margin-right: 0; + visibility: hidden; +} + +.vis.timeline .axis .grid.vertical { + position: absolute; + width: 0; + border-right: 1px solid; +} + +.vis.timeline .axis .grid.horizontal { + position: absolute; + left: 0; + width: 100%; + height: 0; + border-bottom: 1px solid; +} + +.vis.timeline .axis .grid.minor { + border-color: #e5e5e5; +} + +.vis.timeline .axis .grid.major { + border-color: #bfbfbf; +} + +.vis.timeline .currenttime { + background-color: #FF7F6E; + width: 2px; + z-index: 9; +} +.vis.timeline .customtime { + background-color: #6E94FF; + width: 2px; + cursor: move; + z-index: 9; +} +div.graph-manipulationDiv { + border-width:0px; + border-bottom: 1px; + border-style:solid; + border-color: #d6d9d8; + background: #ffffff; /* Old browsers */ + background: -moz-linear-gradient(top, #ffffff 0%, #fcfcfc 48%, #fafafa 50%, #fcfcfc 100%); /* FF3.6+ */ + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#ffffff), color-stop(48%,#fcfcfc), color-stop(50%,#fafafa), color-stop(100%,#fcfcfc)); /* Chrome,Safari4+ */ + background: -webkit-linear-gradient(top, #ffffff 0%,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%); /* Chrome10+,Safari5.1+ */ + background: -o-linear-gradient(top, #ffffff 0%,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%); /* Opera 11.10+ */ + background: -ms-linear-gradient(top, #ffffff 0%,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%); /* IE10+ */ + background: linear-gradient(to bottom, #ffffff 0%,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%); /* W3C */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#fcfcfc',GradientType=0 ); /* IE6-9 */ + + width: 600px; + height:30px; + z-index:10; + position:absolute; +} + +div.graph-manipulation-editMode { + height:30px; + z-index:10; + position:absolute; + margin-top:20px; +} + +div.graph-manipulation-closeDiv { + height:30px; + width:30px; + z-index:11; + position:absolute; + margin-top:3px; + margin-left:590px; + background-position: 0px 0px; + background-repeat:no-repeat; + background-image: url("img/graph/cross.png"); + cursor: pointer; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +span.graph-manipulationUI { + font-family: verdana; + font-size: 12px; + -moz-border-radius: 15px; + border-radius: 15px; + display:inline-block; + background-position: 0px 0px; + background-repeat:no-repeat; + height:24px; + margin: -14px 0px 0px 10px; + vertical-align:middle; + cursor: pointer; + padding: 0px 8px 0px 8px; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +span.graph-manipulationUI:hover { + box-shadow: 1px 1px 8px rgba(0, 0, 0, 0.20); +} + +span.graph-manipulationUI:active { + box-shadow: 1px 1px 8px rgba(0, 0, 0, 0.50); +} + +span.graph-manipulationUI.back { + background-image: url("img/graph/backIcon.png"); +} + +span.graph-manipulationUI.none:hover { + box-shadow: 1px 1px 8px rgba(0, 0, 0, 0.0); + cursor: default; +} +span.graph-manipulationUI.none:active { + box-shadow: 1px 1px 8px rgba(0, 0, 0, 0.0); +} +span.graph-manipulationUI.none { + padding: 0px 0px 0px 0px; +} +span.graph-manipulationUI.notification{ + margin: 2px; + font-weight: bold; +} + +span.graph-manipulationUI.add { + background-image: url("img/graph/addNodeIcon.png"); +} + +span.graph-manipulationUI.edit { + background-image: url("img/graph/editIcon.png"); +} + +span.graph-manipulationUI.edit.editmode { + background-color: #fcfcfc; + border-style:solid; + border-width:1px; + border-color: #cccccc; +} + +span.graph-manipulationUI.connect { + background-image: url("img/graph/connectIcon.png"); +} + +span.graph-manipulationUI.delete { + background-image: url("img/graph/deleteIcon.png"); +} +/* top right bottom left */ +span.graph-manipulationLabel { + margin: 0px 0px 0px 23px; + line-height: 25px; +} +div.graph-seperatorLine { + display:inline-block; + width:1px; + height:20px; + background-color: #bdbdbd; + margin: 5px 7px 0px 15px; +} +div.graph-navigation { + width:34px; + height:34px; + z-index:10; + -moz-border-radius: 17px; + border-radius: 17px; + position:absolute; + display:inline-block; + background-position: 2px 2px; + background-repeat:no-repeat; + cursor: pointer; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +div.graph-navigation:hover { + box-shadow: 0px 0px 3px 3px rgba(56, 207, 21, 0.30); +} + +div.graph-navigation:active { + box-shadow: 0px 0px 1px 3px rgba(56, 207, 21, 0.95); +} + +div.graph-navigation.active { + box-shadow: 0px 0px 1px 3px rgba(56, 207, 21, 0.95); +} + +div.graph-navigation.up { + background-image: url("img/graph/upArrow.png"); + bottom:50px; + left:55px; +} +div.graph-navigation.down { + background-image: url("img/graph/downArrow.png"); + bottom:10px; + left:55px; +} +div.graph-navigation.left { + background-image: url("img/graph/leftArrow.png"); + bottom:10px; + left:15px; +} +div.graph-navigation.right { + background-image: url("img/graph/rightArrow.png"); + bottom:10px; + left:95px; +} +div.graph-navigation.zoomIn { + background-image: url("img/graph/plus.png"); + bottom:10px; + right:15px; +} +div.graph-navigation.zoomOut { + background-image: url("img/graph/minus.png"); + bottom:10px; + right:55px; +} +div.graph-navigation.zoomExtends { + background-image: url("img/graph/zoomExtends.png"); + bottom:50px; + right:15px; +} diff --git a/web/dist/vis.js b/web/dist/vis.js new file mode 100644 index 0000000..9a4279f --- /dev/null +++ b/web/dist/vis.js @@ -0,0 +1,22876 @@ +/** + * vis.js + * https://github.com/almende/vis + * + * A dynamic, browser-based visualization library. + * + * @version 0.7.1 + * @date 2014-03-27 + * + * @license + * Copyright (C) 2011-2014 Almende B.V, http://almende.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +!function(e){if("object"==typeof exports)module.exports=e();else if("function"==typeof define&&define.amd)define(e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.vis=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o>> 0; + + // 4. If IsCallable(callback) is false, throw a TypeError exception. + // See: http://es5.github.com/#x9.11 + if (typeof callback !== "function") { + throw new TypeError(callback + " is not a function"); + } + + // 5. If thisArg was supplied, let T be thisArg; else let T be undefined. + if (thisArg) { + T = thisArg; + } + + // 6. Let A be a new array created as if by the expression new Array(len) where Array is + // the standard built-in constructor with that name and len is the value of len. + A = new Array(len); + + // 7. Let k be 0 + k = 0; + + // 8. Repeat, while k < len + while(k < len) { + + var kValue, mappedValue; + + // a. Let Pk be ToString(k). + // This is implicit for LHS operands of the in operator + // b. Let kPresent be the result of calling the HasProperty internal method of O with argument Pk. + // This step can be combined with c + // c. If kPresent is true, then + if (k in O) { + + // i. Let kValue be the result of calling the Get internal method of O with argument Pk. + kValue = O[ k ]; + + // ii. Let mappedValue be the result of calling the Call internal method of callback + // with T as the this value and argument list containing kValue, k, and O. + mappedValue = callback.call(T, kValue, k, O); + + // iii. Call the DefineOwnProperty internal method of A with arguments + // Pk, Property Descriptor {Value: mappedValue, : true, Enumerable: true, Configurable: true}, + // and false. + + // In browsers that support Object.defineProperty, use the following: + // Object.defineProperty(A, Pk, { value: mappedValue, writable: true, enumerable: true, configurable: true }); + + // For best browser support, use the following: + A[ k ] = mappedValue; + } + // d. Increase k by 1. + k++; + } + + // 9. return A + return A; + }; +} + +// Internet Explorer 8 and older does not support Array.filter, so we define it +// here in that case. +// https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/filter +if (!Array.prototype.filter) { + Array.prototype.filter = function(fun /*, thisp */) { + "use strict"; + + if (this == null) { + throw new TypeError(); + } + + var t = Object(this); + var len = t.length >>> 0; + if (typeof fun != "function") { + throw new TypeError(); + } + + var res = []; + var thisp = arguments[1]; + for (var i = 0; i < len; i++) { + if (i in t) { + var val = t[i]; // in case fun mutates this + if (fun.call(thisp, val, i, t)) + res.push(val); + } + } + + return res; + }; +} + + +// Internet Explorer 8 and older does not support Object.keys, so we define it +// here in that case. +// https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/keys +if (!Object.keys) { + Object.keys = (function () { + var hasOwnProperty = Object.prototype.hasOwnProperty, + hasDontEnumBug = !({toString: null}).propertyIsEnumerable('toString'), + dontEnums = [ + 'toString', + 'toLocaleString', + 'valueOf', + 'hasOwnProperty', + 'isPrototypeOf', + 'propertyIsEnumerable', + 'constructor' + ], + dontEnumsLength = dontEnums.length; + + return function (obj) { + if (typeof obj !== 'object' && typeof obj !== 'function' || obj === null) { + throw new TypeError('Object.keys called on non-object'); + } + + var result = []; + + for (var prop in obj) { + if (hasOwnProperty.call(obj, prop)) result.push(prop); + } + + if (hasDontEnumBug) { + for (var i=0; i < dontEnumsLength; i++) { + if (hasOwnProperty.call(obj, dontEnums[i])) result.push(dontEnums[i]); + } + } + return result; + } + })() +} + +// Internet Explorer 8 and older does not support Array.isArray, +// so we define it here in that case. +// https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/isArray +if(!Array.isArray) { + Array.isArray = function (vArg) { + return Object.prototype.toString.call(vArg) === "[object Array]"; + }; +} + +// Internet Explorer 8 and older does not support Function.bind, +// so we define it here in that case. +// https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/bind +if (!Function.prototype.bind) { + Function.prototype.bind = function (oThis) { + if (typeof this !== "function") { + // closest thing possible to the ECMAScript 5 internal IsCallable function + throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable"); + } + + var aArgs = Array.prototype.slice.call(arguments, 1), + fToBind = this, + fNOP = function () {}, + fBound = function () { + return fToBind.apply(this instanceof fNOP && oThis + ? this + : oThis, + aArgs.concat(Array.prototype.slice.call(arguments))); + }; + + fNOP.prototype = this.prototype; + fBound.prototype = new fNOP(); + + return fBound; + }; +} + +// https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/create +if (!Object.create) { + Object.create = function (o) { + if (arguments.length > 1) { + throw new Error('Object.create implementation only accepts the first parameter.'); + } + function F() {} + F.prototype = o; + return new F(); + }; +} + +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind +if (!Function.prototype.bind) { + Function.prototype.bind = function (oThis) { + if (typeof this !== "function") { + // closest thing possible to the ECMAScript 5 internal IsCallable function + throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable"); + } + + var aArgs = Array.prototype.slice.call(arguments, 1), + fToBind = this, + fNOP = function () {}, + fBound = function () { + return fToBind.apply(this instanceof fNOP && oThis + ? this + : oThis, + aArgs.concat(Array.prototype.slice.call(arguments))); + }; + + fNOP.prototype = this.prototype; + fBound.prototype = new fNOP(); + + return fBound; + }; +} + +/** + * utility functions + */ +var util = {}; + +/** + * Test whether given object is a number + * @param {*} object + * @return {Boolean} isNumber + */ +util.isNumber = function isNumber(object) { + return (object instanceof Number || typeof object == 'number'); +}; + +/** + * Test whether given object is a string + * @param {*} object + * @return {Boolean} isString + */ +util.isString = function isString(object) { + return (object instanceof String || typeof object == 'string'); +}; + +/** + * Test whether given object is a Date, or a String containing a Date + * @param {Date | String} object + * @return {Boolean} isDate + */ +util.isDate = function isDate(object) { + if (object instanceof Date) { + return true; + } + else if (util.isString(object)) { + // test whether this string contains a date + var match = ASPDateRegex.exec(object); + if (match) { + return true; + } + else if (!isNaN(Date.parse(object))) { + return true; + } + } + + return false; +}; + +/** + * Test whether given object is an instance of google.visualization.DataTable + * @param {*} object + * @return {Boolean} isDataTable + */ +util.isDataTable = function isDataTable(object) { + return (typeof (google) !== 'undefined') && + (google.visualization) && + (google.visualization.DataTable) && + (object instanceof google.visualization.DataTable); +}; + +/** + * Create a semi UUID + * source: http://stackoverflow.com/a/105074/1262753 + * @return {String} uuid + */ +util.randomUUID = function randomUUID () { + var S4 = function () { + return Math.floor( + Math.random() * 0x10000 /* 65536 */ + ).toString(16); + }; + + return ( + S4() + S4() + '-' + + S4() + '-' + + S4() + '-' + + S4() + '-' + + S4() + S4() + S4() + ); +}; + +/** + * Extend object a with the properties of object b or a series of objects + * Only properties with defined values are copied + * @param {Object} a + * @param {... Object} b + * @return {Object} a + */ +util.extend = function (a, b) { + for (var i = 1, len = arguments.length; i < len; i++) { + var other = arguments[i]; + for (var prop in other) { + if (other.hasOwnProperty(prop) && other[prop] !== undefined) { + a[prop] = other[prop]; + } + } + } + + return a; +}; + +/** + * Convert an object to another type + * @param {Boolean | Number | String | Date | Moment | Null | undefined} object + * @param {String | undefined} type Name of the type. Available types: + * 'Boolean', 'Number', 'String', + * 'Date', 'Moment', ISODate', 'ASPDate'. + * @return {*} object + * @throws Error + */ +util.convert = function convert(object, type) { + var match; + + if (object === undefined) { + return undefined; + } + if (object === null) { + return null; + } + + if (!type) { + return object; + } + if (!(typeof type === 'string') && !(type instanceof String)) { + throw new Error('Type must be a string'); + } + + //noinspection FallthroughInSwitchStatementJS + switch (type) { + case 'boolean': + case 'Boolean': + return Boolean(object); + + case 'number': + case 'Number': + return Number(object.valueOf()); + + case 'string': + case 'String': + return String(object); + + case 'Date': + if (util.isNumber(object)) { + return new Date(object); + } + if (object instanceof Date) { + return new Date(object.valueOf()); + } + else if (moment.isMoment(object)) { + return new Date(object.valueOf()); + } + if (util.isString(object)) { + match = ASPDateRegex.exec(object); + if (match) { + // object is an ASP date + return new Date(Number(match[1])); // parse number + } + else { + return moment(object).toDate(); // parse string + } + } + else { + throw new Error( + 'Cannot convert object of type ' + util.getType(object) + + ' to type Date'); + } + + case 'Moment': + if (util.isNumber(object)) { + return moment(object); + } + if (object instanceof Date) { + return moment(object.valueOf()); + } + else if (moment.isMoment(object)) { + return moment(object); + } + if (util.isString(object)) { + match = ASPDateRegex.exec(object); + if (match) { + // object is an ASP date + return moment(Number(match[1])); // parse number + } + else { + return moment(object); // parse string + } + } + else { + throw new Error( + 'Cannot convert object of type ' + util.getType(object) + + ' to type Date'); + } + + case 'ISODate': + if (util.isNumber(object)) { + return new Date(object); + } + else if (object instanceof Date) { + return object.toISOString(); + } + else if (moment.isMoment(object)) { + return object.toDate().toISOString(); + } + else if (util.isString(object)) { + match = ASPDateRegex.exec(object); + if (match) { + // object is an ASP date + return new Date(Number(match[1])).toISOString(); // parse number + } + else { + return new Date(object).toISOString(); // parse string + } + } + else { + throw new Error( + 'Cannot convert object of type ' + util.getType(object) + + ' to type ISODate'); + } + + case 'ASPDate': + if (util.isNumber(object)) { + return '/Date(' + object + ')/'; + } + else if (object instanceof Date) { + return '/Date(' + object.valueOf() + ')/'; + } + else if (util.isString(object)) { + match = ASPDateRegex.exec(object); + var value; + if (match) { + // object is an ASP date + value = new Date(Number(match[1])).valueOf(); // parse number + } + else { + value = new Date(object).valueOf(); // parse string + } + return '/Date(' + value + ')/'; + } + else { + throw new Error( + 'Cannot convert object of type ' + util.getType(object) + + ' to type ASPDate'); + } + + default: + throw new Error('Cannot convert object of type ' + util.getType(object) + + ' to type "' + type + '"'); + } +}; + +// parse ASP.Net Date pattern, +// for example '/Date(1198908717056)/' or '/Date(1198908717056-0700)/' +// code from http://momentjs.com/ +var ASPDateRegex = /^\/?Date\((\-?\d+)/i; + +/** + * Get the type of an object, for example util.getType([]) returns 'Array' + * @param {*} object + * @return {String} type + */ +util.getType = function getType(object) { + var type = typeof object; + + if (type == 'object') { + if (object == null) { + return 'null'; + } + if (object instanceof Boolean) { + return 'Boolean'; + } + if (object instanceof Number) { + return 'Number'; + } + if (object instanceof String) { + return 'String'; + } + if (object instanceof Array) { + return 'Array'; + } + if (object instanceof Date) { + return 'Date'; + } + return 'Object'; + } + else if (type == 'number') { + return 'Number'; + } + else if (type == 'boolean') { + return 'Boolean'; + } + else if (type == 'string') { + return 'String'; + } + + return type; +}; + +/** + * Retrieve the absolute left value of a DOM element + * @param {Element} elem A dom element, for example a div + * @return {number} left The absolute left position of this element + * in the browser page. + */ +util.getAbsoluteLeft = function getAbsoluteLeft (elem) { + var doc = document.documentElement; + var body = document.body; + + var left = elem.offsetLeft; + var e = elem.offsetParent; + while (e != null && e != body && e != doc) { + left += e.offsetLeft; + left -= e.scrollLeft; + e = e.offsetParent; + } + return left; +}; + +/** + * Retrieve the absolute top value of a DOM element + * @param {Element} elem A dom element, for example a div + * @return {number} top The absolute top position of this element + * in the browser page. + */ +util.getAbsoluteTop = function getAbsoluteTop (elem) { + var doc = document.documentElement; + var body = document.body; + + var top = elem.offsetTop; + var e = elem.offsetParent; + while (e != null && e != body && e != doc) { + top += e.offsetTop; + top -= e.scrollTop; + e = e.offsetParent; + } + return top; +}; + +/** + * Get the absolute, vertical mouse position from an event. + * @param {Event} event + * @return {Number} pageY + */ +util.getPageY = function getPageY (event) { + if ('pageY' in event) { + return event.pageY; + } + else { + var clientY; + if (('targetTouches' in event) && event.targetTouches.length) { + clientY = event.targetTouches[0].clientY; + } + else { + clientY = event.clientY; + } + + var doc = document.documentElement; + var body = document.body; + return clientY + + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - + ( doc && doc.clientTop || body && body.clientTop || 0 ); + } +}; + +/** + * Get the absolute, horizontal mouse position from an event. + * @param {Event} event + * @return {Number} pageX + */ +util.getPageX = function getPageX (event) { + if ('pageY' in event) { + return event.pageX; + } + else { + var clientX; + if (('targetTouches' in event) && event.targetTouches.length) { + clientX = event.targetTouches[0].clientX; + } + else { + clientX = event.clientX; + } + + var doc = document.documentElement; + var body = document.body; + return clientX + + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - + ( doc && doc.clientLeft || body && body.clientLeft || 0 ); + } +}; + +/** + * add a className to the given elements style + * @param {Element} elem + * @param {String} className + */ +util.addClassName = function addClassName(elem, className) { + var classes = elem.className.split(' '); + if (classes.indexOf(className) == -1) { + classes.push(className); // add the class to the array + elem.className = classes.join(' '); + } +}; + +/** + * add a className to the given elements style + * @param {Element} elem + * @param {String} className + */ +util.removeClassName = function removeClassname(elem, className) { + var classes = elem.className.split(' '); + var index = classes.indexOf(className); + if (index != -1) { + classes.splice(index, 1); // remove the class from the array + elem.className = classes.join(' '); + } +}; + +/** + * For each method for both arrays and objects. + * In case of an array, the built-in Array.forEach() is applied. + * In case of an Object, the method loops over all properties of the object. + * @param {Object | Array} object An Object or Array + * @param {function} callback Callback method, called for each item in + * the object or array with three parameters: + * callback(value, index, object) + */ +util.forEach = function forEach (object, callback) { + var i, + len; + if (object instanceof Array) { + // array + for (i = 0, len = object.length; i < len; i++) { + callback(object[i], i, object); + } + } + else { + // object + for (i in object) { + if (object.hasOwnProperty(i)) { + callback(object[i], i, object); + } + } + } +}; + +/** + * Update a property in an object + * @param {Object} object + * @param {String} key + * @param {*} value + * @return {Boolean} changed + */ +util.updateProperty = function updateProp (object, key, value) { + if (object[key] !== value) { + object[key] = value; + return true; + } + else { + return false; + } +}; + +/** + * Add and event listener. Works for all browsers + * @param {Element} element An html element + * @param {string} action The action, for example "click", + * without the prefix "on" + * @param {function} listener The callback function to be executed + * @param {boolean} [useCapture] + */ +util.addEventListener = function addEventListener(element, action, listener, useCapture) { + if (element.addEventListener) { + if (useCapture === undefined) + useCapture = false; + + if (action === "mousewheel" && navigator.userAgent.indexOf("Firefox") >= 0) { + action = "DOMMouseScroll"; // For Firefox + } + + element.addEventListener(action, listener, useCapture); + } else { + element.attachEvent("on" + action, listener); // IE browsers + } +}; + +/** + * Remove an event listener from an element + * @param {Element} element An html dom element + * @param {string} action The name of the event, for example "mousedown" + * @param {function} listener The listener function + * @param {boolean} [useCapture] + */ +util.removeEventListener = function removeEventListener(element, action, listener, useCapture) { + if (element.removeEventListener) { + // non-IE browsers + if (useCapture === undefined) + useCapture = false; + + if (action === "mousewheel" && navigator.userAgent.indexOf("Firefox") >= 0) { + action = "DOMMouseScroll"; // For Firefox + } + + element.removeEventListener(action, listener, useCapture); + } else { + // IE browsers + element.detachEvent("on" + action, listener); + } +}; + + +/** + * Get HTML element which is the target of the event + * @param {Event} event + * @return {Element} target element + */ +util.getTarget = function getTarget(event) { + // code from http://www.quirksmode.org/js/events_properties.html + if (!event) { + event = window.event; + } + + var target; + + if (event.target) { + target = event.target; + } + else if (event.srcElement) { + target = event.srcElement; + } + + if (target.nodeType != undefined && target.nodeType == 3) { + // defeat Safari bug + target = target.parentNode; + } + + return target; +}; + +/** + * Fake a hammer.js gesture. Event can be a ScrollEvent or MouseMoveEvent + * @param {Element} element + * @param {Event} event + */ +util.fakeGesture = function fakeGesture (element, event) { + var eventType = null; + + // for hammer.js 1.0.5 + var gesture = Hammer.event.collectEventData(this, eventType, event); + + // for hammer.js 1.0.6 + //var touches = Hammer.event.getTouchList(event, eventType); + // var gesture = Hammer.event.collectEventData(this, eventType, touches, event); + + // on IE in standards mode, no touches are recognized by hammer.js, + // resulting in NaN values for center.pageX and center.pageY + if (isNaN(gesture.center.pageX)) { + gesture.center.pageX = event.pageX; + } + if (isNaN(gesture.center.pageY)) { + gesture.center.pageY = event.pageY; + } + + return gesture; +}; + +util.option = {}; + +/** + * Convert a value into a boolean + * @param {Boolean | function | undefined} value + * @param {Boolean} [defaultValue] + * @returns {Boolean} bool + */ +util.option.asBoolean = function (value, defaultValue) { + if (typeof value == 'function') { + value = value(); + } + + if (value != null) { + return (value != false); + } + + return defaultValue || null; +}; + +/** + * Convert a value into a number + * @param {Boolean | function | undefined} value + * @param {Number} [defaultValue] + * @returns {Number} number + */ +util.option.asNumber = function (value, defaultValue) { + if (typeof value == 'function') { + value = value(); + } + + if (value != null) { + return Number(value) || defaultValue || null; + } + + return defaultValue || null; +}; + +/** + * Convert a value into a string + * @param {String | function | undefined} value + * @param {String} [defaultValue] + * @returns {String} str + */ +util.option.asString = function (value, defaultValue) { + if (typeof value == 'function') { + value = value(); + } + + if (value != null) { + return String(value); + } + + return defaultValue || null; +}; + +/** + * Convert a size or location into a string with pixels or a percentage + * @param {String | Number | function | undefined} value + * @param {String} [defaultValue] + * @returns {String} size + */ +util.option.asSize = function (value, defaultValue) { + if (typeof value == 'function') { + value = value(); + } + + if (util.isString(value)) { + return value; + } + else if (util.isNumber(value)) { + return value + 'px'; + } + else { + return defaultValue || null; + } +}; + +/** + * Convert a value into a DOM element + * @param {HTMLElement | function | undefined} value + * @param {HTMLElement} [defaultValue] + * @returns {HTMLElement | null} dom + */ +util.option.asElement = function (value, defaultValue) { + if (typeof value == 'function') { + value = value(); + } + + return value || defaultValue || null; +}; + + + +util.GiveDec = function GiveDec(Hex) +{ + if(Hex == "A") + Value = 10; + else + if(Hex == "B") + Value = 11; + else + if(Hex == "C") + Value = 12; + else + if(Hex == "D") + Value = 13; + else + if(Hex == "E") + Value = 14; + else + if(Hex == "F") + Value = 15; + else + Value = eval(Hex) + return Value; +} + +util.GiveHex = function GiveHex(Dec) +{ + if(Dec == 10) + Value = "A"; + else + if(Dec == 11) + Value = "B"; + else + if(Dec == 12) + Value = "C"; + else + if(Dec == 13) + Value = "D"; + else + if(Dec == 14) + Value = "E"; + else + if(Dec == 15) + Value = "F"; + else + Value = "" + Dec; + return Value; +} + +/** + * http://www.yellowpipe.com/yis/tools/hex-to-rgb/color-converter.php + * + * @param {String} hex + * @returns {{r: *, g: *, b: *}} + */ +util.hexToRGB = function hexToRGB(hex) { + hex = hex.replace("#","").toUpperCase(); + + var a = util.GiveDec(hex.substring(0, 1)); + var b = util.GiveDec(hex.substring(1, 2)); + var c = util.GiveDec(hex.substring(2, 3)); + var d = util.GiveDec(hex.substring(3, 4)); + var e = util.GiveDec(hex.substring(4, 5)); + var f = util.GiveDec(hex.substring(5, 6)); + + var r = (a * 16) + b; + var g = (c * 16) + d; + var b = (e * 16) + f; + + return {r:r,g:g,b:b}; +}; + +util.RGBToHex = function RGBToHex(red,green,blue) { + var a = util.GiveHex(Math.floor(red / 16)); + var b = util.GiveHex(red % 16); + var c = util.GiveHex(Math.floor(green / 16)); + var d = util.GiveHex(green % 16); + var e = util.GiveHex(Math.floor(blue / 16)); + var f = util.GiveHex(blue % 16); + + var hex = a + b + c + d + e + f; + return "#" + hex; +}; + + +/** + * http://www.javascripter.net/faq/rgb2hsv.htm + * + * @param red + * @param green + * @param blue + * @returns {*} + * @constructor + */ +util.RGBToHSV = function RGBToHSV (red,green,blue) { + red=red/255; green=green/255; blue=blue/255; + var minRGB = Math.min(red,Math.min(green,blue)); + var maxRGB = Math.max(red,Math.max(green,blue)); + + // Black-gray-white + if (minRGB == maxRGB) { + return {h:0,s:0,v:minRGB}; + } + + // Colors other than black-gray-white: + var d = (red==minRGB) ? green-blue : ((blue==minRGB) ? red-green : blue-red); + var h = (red==minRGB) ? 3 : ((blue==minRGB) ? 1 : 5); + var hue = 60*(h - d/(maxRGB - minRGB))/360; + var saturation = (maxRGB - minRGB)/maxRGB; + var value = maxRGB; + return {h:hue,s:saturation,v:value}; +}; + + +/** + * https://gist.github.com/mjijackson/5311256 + * @param hue + * @param saturation + * @param value + * @returns {{r: number, g: number, b: number}} + * @constructor + */ +util.HSVToRGB = function HSVToRGB(h, s, v) { + var r, g, b; + + var i = Math.floor(h * 6); + var f = h * 6 - i; + var p = v * (1 - s); + var q = v * (1 - f * s); + var t = v * (1 - (1 - f) * s); + + switch (i % 6) { + case 0: r = v, g = t, b = p; break; + case 1: r = q, g = v, b = p; break; + case 2: r = p, g = v, b = t; break; + case 3: r = p, g = q, b = v; break; + case 4: r = t, g = p, b = v; break; + case 5: r = v, g = p, b = q; break; + } + + return {r:Math.floor(r * 255), g:Math.floor(g * 255), b:Math.floor(b * 255) }; +}; + +util.HSVToHex = function HSVToHex(h,s,v) { + var rgb = util.HSVToRGB(h,s,v); + return util.RGBToHex(rgb.r,rgb.g,rgb.b); +} + +util.hexToHSV = function hexToHSV(hex) { + var rgb = util.hexToRGB(hex); + return util.RGBToHSV(rgb.r,rgb.g,rgb.b); +} + +util.isValidHex = function isValidHex(hex) { + var isOk = /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(hex); + return isOk; +} + +util.copyObject = function copyObject(objectFrom,objectTo) { + for (var i in objectFrom) { + if (objectFrom.hasOwnProperty(i)) { + if (typeof objectFrom[i] == "object") { + objectTo[i] = {}; + util.copyObject(objectFrom[i],objectTo[i]); + } + else { + objectTo[i] = objectFrom[i]; + } + } + } +} + +/** + * DataSet + * + * Usage: + * var dataSet = new DataSet({ + * fieldId: '_id', + * convert: { + * // ... + * } + * }); + * + * dataSet.add(item); + * dataSet.add(data); + * dataSet.update(item); + * dataSet.update(data); + * dataSet.remove(id); + * dataSet.remove(ids); + * var data = dataSet.get(); + * var data = dataSet.get(id); + * var data = dataSet.get(ids); + * var data = dataSet.get(ids, options, data); + * dataSet.clear(); + * + * A data set can: + * - add/remove/update data + * - gives triggers upon changes in the data + * - can import/export data in various data formats + * + * @param {Object} [options] Available options: + * {String} fieldId Field name of the id in the + * items, 'id' by default. + * {Object.} [convert] + * {String[]} [fields] field names to be returned + * {function} [filter] filter items + * {String | function} [order] Order the items by + * a field name or custom sort function. + * {Array | DataTable} [data] If provided, items will be appended to this + * array or table. Required in case of Google + * DataTable. + * + * @throws Error + */ +DataSet.prototype.get = function (args) { + var me = this; + var globalShowInternalIds = this.showInternalIds; + + // parse the arguments + var id, ids, options, data; + var firstType = util.getType(arguments[0]); + if (firstType == 'String' || firstType == 'Number') { + // get(id [, options] [, data]) + id = arguments[0]; + options = arguments[1]; + data = arguments[2]; + } + else if (firstType == 'Array') { + // get(ids [, options] [, data]) + ids = arguments[0]; + options = arguments[1]; + data = arguments[2]; + } + else { + // get([, options] [, data]) + options = arguments[0]; + data = arguments[1]; + } + + // determine the return type + var type; + if (options && options.type) { + type = (options.type == 'DataTable') ? 'DataTable' : 'Array'; + + if (data && (type != util.getType(data))) { + throw new Error('Type of parameter "data" (' + util.getType(data) + ') ' + + 'does not correspond with specified options.type (' + options.type + ')'); + } + if (type == 'DataTable' && !util.isDataTable(data)) { + throw new Error('Parameter "data" must be a DataTable ' + + 'when options.type is "DataTable"'); + } + } + else if (data) { + type = (util.getType(data) == 'DataTable') ? 'DataTable' : 'Array'; + } + else { + type = 'Array'; + } + + // we allow the setting of this value for a single get request. + if (options != undefined) { + if (options.showInternalIds != undefined) { + this.showInternalIds = options.showInternalIds; + } + } + + // build options + var convert = options && options.convert || this.options.convert; + var filter = options && options.filter; + var items = [], item, itemId, i, len; + + // convert items + if (id != undefined) { + // return a single item + item = me._getItem(id, convert); + if (filter && !filter(item)) { + item = null; + } + } + else if (ids != undefined) { + // return a subset of items + for (i = 0, len = ids.length; i < len; i++) { + item = me._getItem(ids[i], convert); + if (!filter || filter(item)) { + items.push(item); + } + } + } + else { + // return all items + for (itemId in this.data) { + if (this.data.hasOwnProperty(itemId)) { + item = me._getItem(itemId, convert); + if (!filter || filter(item)) { + items.push(item); + } + } + } + } + + // restore the global value of showInternalIds + this.showInternalIds = globalShowInternalIds; + + // order the results + if (options && options.order && id == undefined) { + this._sort(items, options.order); + } + + // filter fields of the items + if (options && options.fields) { + var fields = options.fields; + if (id != undefined) { + item = this._filterFields(item, fields); + } + else { + for (i = 0, len = items.length; i < len; i++) { + items[i] = this._filterFields(items[i], fields); + } + } + } + + // return the results + if (type == 'DataTable') { + var columns = this._getColumnNames(data); + if (id != undefined) { + // append a single item to the data table + me._appendRow(data, columns, item); + } + else { + // copy the items to the provided data table + for (i = 0, len = items.length; i < len; i++) { + me._appendRow(data, columns, items[i]); + } + } + return data; + } + else { + // return an array + if (id != undefined) { + // a single item + return item; + } + else { + // multiple items + if (data) { + // copy the items to the provided array + for (i = 0, len = items.length; i < len; i++) { + data.push(items[i]); + } + return data; + } + else { + // just return our array + return items; + } + } + } +}; + +/** + * Get ids of all items or from a filtered set of items. + * @param {Object} [options] An Object with options. Available options: + * {function} [filter] filter items + * {String | function} [order] Order the items by + * a field name or custom sort function. + * @return {Array} ids + */ +DataSet.prototype.getIds = function (options) { + var data = this.data, + filter = options && options.filter, + order = options && options.order, + convert = options && options.convert || this.options.convert, + i, + len, + id, + item, + items, + ids = []; + + if (filter) { + // get filtered items + if (order) { + // create ordered list + items = []; + for (id in data) { + if (data.hasOwnProperty(id)) { + item = this._getItem(id, convert); + if (filter(item)) { + items.push(item); + } + } + } + + this._sort(items, order); + + for (i = 0, len = items.length; i < len; i++) { + ids[i] = items[i][this.fieldId]; + } + } + else { + // create unordered list + for (id in data) { + if (data.hasOwnProperty(id)) { + item = this._getItem(id, convert); + if (filter(item)) { + ids.push(item[this.fieldId]); + } + } + } + } + } + else { + // get all items + if (order) { + // create an ordered list + items = []; + for (id in data) { + if (data.hasOwnProperty(id)) { + items.push(data[id]); + } + } + + this._sort(items, order); + + for (i = 0, len = items.length; i < len; i++) { + ids[i] = items[i][this.fieldId]; + } + } + else { + // create unordered list + for (id in data) { + if (data.hasOwnProperty(id)) { + item = data[id]; + ids.push(item[this.fieldId]); + } + } + } + } + + return ids; +}; + +/** + * Execute a callback function for every item in the dataset. + * The order of the items is not determined. + * @param {function} callback + * @param {Object} [options] Available options: + * {Object.} [convert] + * {String[]} [fields] filter fields + * {function} [filter] filter items + * {String | function} [order] Order the items by + * a field name or custom sort function. + */ +DataSet.prototype.forEach = function (callback, options) { + var filter = options && options.filter, + convert = options && options.convert || this.options.convert, + data = this.data, + item, + id; + + if (options && options.order) { + // execute forEach on ordered list + var items = this.get(options); + + for (var i = 0, len = items.length; i < len; i++) { + item = items[i]; + id = item[this.fieldId]; + callback(item, id); + } + } + else { + // unordered + for (id in data) { + if (data.hasOwnProperty(id)) { + item = this._getItem(id, convert); + if (!filter || filter(item)) { + callback(item, id); + } + } + } + } +}; + +/** + * Map every item in the dataset. + * @param {function} callback + * @param {Object} [options] Available options: + * {Object.} [convert] + * {String[]} [fields] filter fields + * {function} [filter] filter items + * {String | function} [order] Order the items by + * a field name or custom sort function. + * @return {Object[]} mappedItems + */ +DataSet.prototype.map = function (callback, options) { + var filter = options && options.filter, + convert = options && options.convert || this.options.convert, + mappedItems = [], + data = this.data, + item; + + // convert and filter items + for (var id in data) { + if (data.hasOwnProperty(id)) { + item = this._getItem(id, convert); + if (!filter || filter(item)) { + mappedItems.push(callback(item, id)); + } + } + } + + // order items + if (options && options.order) { + this._sort(mappedItems, options.order); + } + + return mappedItems; +}; + +/** + * Filter the fields of an item + * @param {Object} item + * @param {String[]} fields Field names + * @return {Object} filteredItem + * @private + */ +DataSet.prototype._filterFields = function (item, fields) { + var filteredItem = {}; + + for (var field in item) { + if (item.hasOwnProperty(field) && (fields.indexOf(field) != -1)) { + filteredItem[field] = item[field]; + } + } + + return filteredItem; +}; + +/** + * Sort the provided array with items + * @param {Object[]} items + * @param {String | function} order A field name or custom sort function. + * @private + */ +DataSet.prototype._sort = function (items, order) { + if (util.isString(order)) { + // order by provided field name + var name = order; // field name + items.sort(function (a, b) { + var av = a[name]; + var bv = b[name]; + return (av > bv) ? 1 : ((av < bv) ? -1 : 0); + }); + } + else if (typeof order === 'function') { + // order by sort function + items.sort(order); + } + // TODO: extend order by an Object {field:String, direction:String} + // where direction can be 'asc' or 'desc' + else { + throw new TypeError('Order must be a function or a string'); + } +}; + +/** + * Remove an object by pointer or by id + * @param {String | Number | Object | Array} id Object or id, or an array with + * objects or ids to be removed + * @param {String} [senderId] Optional sender id + * @return {Array} removedIds + */ +DataSet.prototype.remove = function (id, senderId) { + var removedIds = [], + i, len, removedId; + + if (id instanceof Array) { + for (i = 0, len = id.length; i < len; i++) { + removedId = this._remove(id[i]); + if (removedId != null) { + removedIds.push(removedId); + } + } + } + else { + removedId = this._remove(id); + if (removedId != null) { + removedIds.push(removedId); + } + } + + if (removedIds.length) { + this._trigger('remove', {items: removedIds}, senderId); + } + + return removedIds; +}; + +/** + * Remove an item by its id + * @param {Number | String | Object} id id or item + * @returns {Number | String | null} id + * @private + */ +DataSet.prototype._remove = function (id) { + if (util.isNumber(id) || util.isString(id)) { + if (this.data[id]) { + delete this.data[id]; + delete this.internalIds[id]; + return id; + } + } + else if (id instanceof Object) { + var itemId = id[this.fieldId]; + if (itemId && this.data[itemId]) { + delete this.data[itemId]; + delete this.internalIds[itemId]; + return itemId; + } + } + return null; +}; + +/** + * Clear the data + * @param {String} [senderId] Optional sender id + * @return {Array} removedIds The ids of all removed items + */ +DataSet.prototype.clear = function (senderId) { + var ids = Object.keys(this.data); + + this.data = {}; + this.internalIds = {}; + + this._trigger('remove', {items: ids}, senderId); + + return ids; +}; + +/** + * Find the item with maximum value of a specified field + * @param {String} field + * @return {Object | null} item Item containing max value, or null if no items + */ +DataSet.prototype.max = function (field) { + var data = this.data, + max = null, + maxField = null; + + for (var id in data) { + if (data.hasOwnProperty(id)) { + var item = data[id]; + var itemField = item[field]; + if (itemField != null && (!max || itemField > maxField)) { + max = item; + maxField = itemField; + } + } + } + + return max; +}; + +/** + * Find the item with minimum value of a specified field + * @param {String} field + * @return {Object | null} item Item containing max value, or null if no items + */ +DataSet.prototype.min = function (field) { + var data = this.data, + min = null, + minField = null; + + for (var id in data) { + if (data.hasOwnProperty(id)) { + var item = data[id]; + var itemField = item[field]; + if (itemField != null && (!min || itemField < minField)) { + min = item; + minField = itemField; + } + } + } + + return min; +}; + +/** + * Find all distinct values of a specified field + * @param {String} field + * @return {Array} values Array containing all distinct values. If the data + * items do not contain the specified field, an array + * containing a single value undefined is returned. + * The returned array is unordered. + */ +DataSet.prototype.distinct = function (field) { + var data = this.data, + values = [], + fieldType = this.options.convert[field], + count = 0; + + for (var prop in data) { + if (data.hasOwnProperty(prop)) { + var item = data[prop]; + var value = util.convert(item[field], fieldType); + var exists = false; + for (var i = 0; i < count; i++) { + if (values[i] == value) { + exists = true; + break; + } + } + if (!exists) { + values[count] = value; + count++; + } + } + } + + return values; +}; + +/** + * Add a single item. Will fail when an item with the same id already exists. + * @param {Object} item + * @return {String} id + * @private + */ +DataSet.prototype._addItem = function (item) { + var id = item[this.fieldId]; + + if (id != undefined) { + // check whether this id is already taken + if (this.data[id]) { + // item already exists + throw new Error('Cannot add item: item with id ' + id + ' already exists'); + } + } + else { + // generate an id + id = util.randomUUID(); + item[this.fieldId] = id; + this.internalIds[id] = item; + } + + var d = {}; + for (var field in item) { + if (item.hasOwnProperty(field)) { + var fieldType = this.convert[field]; // type may be undefined + d[field] = util.convert(item[field], fieldType); + } + } + this.data[id] = d; + + return id; +}; + +/** + * Get an item. Fields can be converted to a specific type + * @param {String} id + * @param {Object.} [convert] field types to convert + * @return {Object | null} item + * @private + */ +DataSet.prototype._getItem = function (id, convert) { + var field, value; + + // get the item from the dataset + var raw = this.data[id]; + if (!raw) { + return null; + } + + // convert the items field types + var converted = {}, + fieldId = this.fieldId, + internalIds = this.internalIds; + if (convert) { + for (field in raw) { + if (raw.hasOwnProperty(field)) { + value = raw[field]; + // output all fields, except internal ids + if ((field != fieldId) || (!(value in internalIds) || this.showInternalIds)) { + converted[field] = util.convert(value, convert[field]); + } + } + } + } + else { + // no field types specified, no converting needed + for (field in raw) { + if (raw.hasOwnProperty(field)) { + value = raw[field]; + // output all fields, except internal ids + if ((field != fieldId) || (!(value in internalIds) || this.showInternalIds)) { + converted[field] = value; + } + } + } + } + return converted; +}; + +/** + * Update a single item: merge with existing item. + * Will fail when the item has no id, or when there does not exist an item + * with the same id. + * @param {Object} item + * @return {String} id + * @private + */ +DataSet.prototype._updateItem = function (item) { + var id = item[this.fieldId]; + if (id == undefined) { + throw new Error('Cannot update item: item has no id (item: ' + JSON.stringify(item) + ')'); + } + var d = this.data[id]; + if (!d) { + // item doesn't exist + throw new Error('Cannot update item: no item with id ' + id + ' found'); + } + + // merge with current item + for (var field in item) { + if (item.hasOwnProperty(field)) { + var fieldType = this.convert[field]; // type may be undefined + d[field] = util.convert(item[field], fieldType); + } + } + + return id; +}; + +/** + * check if an id is an internal or external id + * @param id + * @returns {boolean} + * @private + */ +DataSet.prototype.isInternalId = function(id) { + return (id in this.internalIds); +}; + + +/** + * Get an array with the column names of a Google DataTable + * @param {DataTable} dataTable + * @return {String[]} columnNames + * @private + */ +DataSet.prototype._getColumnNames = function (dataTable) { + var columns = []; + for (var col = 0, cols = dataTable.getNumberOfColumns(); col < cols; col++) { + columns[col] = dataTable.getColumnId(col) || dataTable.getColumnLabel(col); + } + return columns; +}; + +/** + * Append an item as a row to the dataTable + * @param dataTable + * @param columns + * @param item + * @private + */ +DataSet.prototype._appendRow = function (dataTable, columns, item) { + var row = dataTable.addRow(); + + for (var col = 0, cols = columns.length; col < cols; col++) { + var field = columns[col]; + dataTable.setValue(row, col, item[field]); + } +}; + +/** + * DataView + * + * a dataview offers a filtered view on a dataset or an other dataview. + * + * @param {DataSet | DataView} data + * @param {Object} [options] Available options: see method get + * + * @constructor DataView + */ +function DataView (data, options) { + this.id = util.randomUUID(); + + this.data = null; + this.ids = {}; // ids of the items currently in memory (just contains a boolean true) + this.options = options || {}; + this.fieldId = 'id'; // name of the field containing id + this.subscribers = {}; // event subscribers + + var me = this; + this.listener = function () { + me._onEvent.apply(me, arguments); + }; + + this.setData(data); +} + +// TODO: implement a function .config() to dynamically update things like configured filter +// and trigger changes accordingly + +/** + * Set a data source for the view + * @param {DataSet | DataView} data + */ +DataView.prototype.setData = function (data) { + var ids, dataItems, i, len; + + if (this.data) { + // unsubscribe from current dataset + if (this.data.unsubscribe) { + this.data.unsubscribe('*', this.listener); + } + + // trigger a remove of all items in memory + ids = []; + for (var id in this.ids) { + if (this.ids.hasOwnProperty(id)) { + ids.push(id); + } + } + this.ids = {}; + this._trigger('remove', {items: ids}); + } + + this.data = data; + + if (this.data) { + // update fieldId + this.fieldId = this.options.fieldId || + (this.data && this.data.options && this.data.options.fieldId) || + 'id'; + + // trigger an add of all added items + ids = this.data.getIds({filter: this.options && this.options.filter}); + for (i = 0, len = ids.length; i < len; i++) { + id = ids[i]; + this.ids[id] = true; + } + this._trigger('add', {items: ids}); + + // subscribe to new dataset + if (this.data.on) { + this.data.on('*', this.listener); + } + } +}; + +/** + * Get data from the data view + * + * Usage: + * + * get() + * get(options: Object) + * get(options: Object, data: Array | DataTable) + * + * get(id: Number) + * get(id: Number, options: Object) + * get(id: Number, options: Object, data: Array | DataTable) + * + * get(ids: Number[]) + * get(ids: Number[], options: Object) + * get(ids: Number[], options: Object, data: Array | DataTable) + * + * Where: + * + * {Number | String} id The id of an item + * {Number[] | String{}} ids An array with ids of items + * {Object} options An Object with options. Available options: + * {String} [type] Type of data to be returned. Can + * be 'DataTable' or 'Array' (default) + * {Object.} [convert] + * {String[]} [fields] field names to be returned + * {function} [filter] filter items + * {String | function} [order] Order the items by + * a field name or custom sort function. + * {Array | DataTable} [data] If provided, items will be appended to this + * array or table. Required in case of Google + * DataTable. + * @param args + */ +DataView.prototype.get = function (args) { + var me = this; + + // parse the arguments + var ids, options, data; + var firstType = util.getType(arguments[0]); + if (firstType == 'String' || firstType == 'Number' || firstType == 'Array') { + // get(id(s) [, options] [, data]) + ids = arguments[0]; // can be a single id or an array with ids + options = arguments[1]; + data = arguments[2]; + } + else { + // get([, options] [, data]) + options = arguments[0]; + data = arguments[1]; + } + + // extend the options with the default options and provided options + var viewOptions = util.extend({}, this.options, options); + + // create a combined filter method when needed + if (this.options.filter && options && options.filter) { + viewOptions.filter = function (item) { + return me.options.filter(item) && options.filter(item); + } + } + + // build up the call to the linked data set + var getArguments = []; + if (ids != undefined) { + getArguments.push(ids); + } + getArguments.push(viewOptions); + getArguments.push(data); + + return this.data && this.data.get.apply(this.data, getArguments); +}; + +/** + * Get ids of all items or from a filtered set of items. + * @param {Object} [options] An Object with options. Available options: + * {function} [filter] filter items + * {String | function} [order] Order the items by + * a field name or custom sort function. + * @return {Array} ids + */ +DataView.prototype.getIds = function (options) { + var ids; + + if (this.data) { + var defaultFilter = this.options.filter; + var filter; + + if (options && options.filter) { + if (defaultFilter) { + filter = function (item) { + return defaultFilter(item) && options.filter(item); + } + } + else { + filter = options.filter; + } + } + else { + filter = defaultFilter; + } + + ids = this.data.getIds({ + filter: filter, + order: options && options.order + }); + } + else { + ids = []; + } + + return ids; +}; + +/** + * Event listener. Will propagate all events from the connected data set to + * the subscribers of the DataView, but will filter the items and only trigger + * when there are changes in the filtered data set. + * @param {String} event + * @param {Object | null} params + * @param {String} senderId + * @private + */ +DataView.prototype._onEvent = function (event, params, senderId) { + var i, len, id, item, + ids = params && params.items, + data = this.data, + added = [], + updated = [], + removed = []; + + if (ids && data) { + switch (event) { + case 'add': + // filter the ids of the added items + for (i = 0, len = ids.length; i < len; i++) { + id = ids[i]; + item = this.get(id); + if (item) { + this.ids[id] = true; + added.push(id); + } + } + + break; + + case 'update': + // determine the event from the views viewpoint: an updated + // item can be added, updated, or removed from this view. + for (i = 0, len = ids.length; i < len; i++) { + id = ids[i]; + item = this.get(id); + + if (item) { + if (this.ids[id]) { + updated.push(id); + } + else { + this.ids[id] = true; + added.push(id); + } + } + else { + if (this.ids[id]) { + delete this.ids[id]; + removed.push(id); + } + else { + // nothing interesting for me :-( + } + } + } + + break; + + case 'remove': + // filter the ids of the removed items + for (i = 0, len = ids.length; i < len; i++) { + id = ids[i]; + if (this.ids[id]) { + delete this.ids[id]; + removed.push(id); + } + } + + break; + } + + if (added.length) { + this._trigger('add', {items: added}, senderId); + } + if (updated.length) { + this._trigger('update', {items: updated}, senderId); + } + if (removed.length) { + this._trigger('remove', {items: removed}, senderId); + } + } +}; + +// copy subscription functionality from DataSet +DataView.prototype.on = DataSet.prototype.on; +DataView.prototype.off = DataSet.prototype.off; +DataView.prototype._trigger = DataSet.prototype._trigger; + +// TODO: make these functions deprecated (replaced with `on` and `off` since version 0.5) +DataView.prototype.subscribe = DataView.prototype.on; +DataView.prototype.unsubscribe = DataView.prototype.off; + +/** + * @constructor TimeStep + * The class TimeStep is an iterator for dates. You provide a start date and an + * end date. The class itself determines the best scale (step size) based on the + * provided start Date, end Date, and minimumStep. + * + * If minimumStep is provided, the step size is chosen as close as possible + * to the minimumStep but larger than minimumStep. If minimumStep is not + * provided, the scale is set to 1 DAY. + * The minimumStep should correspond with the onscreen size of about 6 characters + * + * Alternatively, you can set a scale by hand. + * After creation, you can initialize the class by executing first(). Then you + * can iterate from the start date to the end date via next(). You can check if + * the end date is reached with the function hasNext(). After each step, you can + * retrieve the current date via getCurrent(). + * The TimeStep has scales ranging from milliseconds, seconds, minutes, hours, + * days, to years. + * + * Version: 1.2 + * + * @param {Date} [start] The start date, for example new Date(2010, 9, 21) + * or new Date(2010, 9, 21, 23, 45, 00) + * @param {Date} [end] The end date + * @param {Number} [minimumStep] Optional. Minimum step size in milliseconds + */ +TimeStep = function(start, end, minimumStep) { + // variables + this.current = new Date(); + this._start = new Date(); + this._end = new Date(); + + this.autoScale = true; + this.scale = TimeStep.SCALE.DAY; + this.step = 1; + + // initialize the range + this.setRange(start, end, minimumStep); +}; + +/// enum scale +TimeStep.SCALE = { + MILLISECOND: 1, + SECOND: 2, + MINUTE: 3, + HOUR: 4, + DAY: 5, + WEEKDAY: 6, + MONTH: 7, + YEAR: 8 +}; + + +/** + * Set a new range + * If minimumStep is provided, the step size is chosen as close as possible + * to the minimumStep but larger than minimumStep. If minimumStep is not + * provided, the scale is set to 1 DAY. + * The minimumStep should correspond with the onscreen size of about 6 characters + * @param {Date} [start] The start date and time. + * @param {Date} [end] The end date and time. + * @param {int} [minimumStep] Optional. Minimum step size in milliseconds + */ +TimeStep.prototype.setRange = function(start, end, minimumStep) { + if (!(start instanceof Date) || !(end instanceof Date)) { + throw "No legal start or end date in method setRange"; + } + + this._start = (start != undefined) ? new Date(start.valueOf()) : new Date(); + this._end = (end != undefined) ? new Date(end.valueOf()) : new Date(); + + if (this.autoScale) { + this.setMinimumStep(minimumStep); + } +}; + +/** + * Set the range iterator to the start date. + */ +TimeStep.prototype.first = function() { + this.current = new Date(this._start.valueOf()); + this.roundToMinor(); +}; + +/** + * Round the current date to the first minor date value + * This must be executed once when the current date is set to start Date + */ +TimeStep.prototype.roundToMinor = function() { + // round to floor + // IMPORTANT: we have no breaks in this switch! (this is no bug) + //noinspection FallthroughInSwitchStatementJS + switch (this.scale) { + case TimeStep.SCALE.YEAR: + this.current.setFullYear(this.step * Math.floor(this.current.getFullYear() / this.step)); + this.current.setMonth(0); + case TimeStep.SCALE.MONTH: this.current.setDate(1); + case TimeStep.SCALE.DAY: // intentional fall through + case TimeStep.SCALE.WEEKDAY: this.current.setHours(0); + case TimeStep.SCALE.HOUR: this.current.setMinutes(0); + case TimeStep.SCALE.MINUTE: this.current.setSeconds(0); + case TimeStep.SCALE.SECOND: this.current.setMilliseconds(0); + //case TimeStep.SCALE.MILLISECOND: // nothing to do for milliseconds + } + + if (this.step != 1) { + // round down to the first minor value that is a multiple of the current step size + switch (this.scale) { + case TimeStep.SCALE.MILLISECOND: this.current.setMilliseconds(this.current.getMilliseconds() - this.current.getMilliseconds() % this.step); break; + case TimeStep.SCALE.SECOND: this.current.setSeconds(this.current.getSeconds() - this.current.getSeconds() % this.step); break; + case TimeStep.SCALE.MINUTE: this.current.setMinutes(this.current.getMinutes() - this.current.getMinutes() % this.step); break; + case TimeStep.SCALE.HOUR: this.current.setHours(this.current.getHours() - this.current.getHours() % this.step); break; + case TimeStep.SCALE.WEEKDAY: // intentional fall through + case TimeStep.SCALE.DAY: this.current.setDate((this.current.getDate()-1) - (this.current.getDate()-1) % this.step + 1); break; + case TimeStep.SCALE.MONTH: this.current.setMonth(this.current.getMonth() - this.current.getMonth() % this.step); break; + case TimeStep.SCALE.YEAR: this.current.setFullYear(this.current.getFullYear() - this.current.getFullYear() % this.step); break; + default: break; + } + } +}; + +/** + * Check if the there is a next step + * @return {boolean} true if the current date has not passed the end date + */ +TimeStep.prototype.hasNext = function () { + return (this.current.valueOf() <= this._end.valueOf()); +}; + +/** + * Do the next step + */ +TimeStep.prototype.next = function() { + var prev = this.current.valueOf(); + + // Two cases, needed to prevent issues with switching daylight savings + // (end of March and end of October) + if (this.current.getMonth() < 6) { + switch (this.scale) { + case TimeStep.SCALE.MILLISECOND: + + this.current = new Date(this.current.valueOf() + this.step); break; + case TimeStep.SCALE.SECOND: this.current = new Date(this.current.valueOf() + this.step * 1000); break; + case TimeStep.SCALE.MINUTE: this.current = new Date(this.current.valueOf() + this.step * 1000 * 60); break; + case TimeStep.SCALE.HOUR: + this.current = new Date(this.current.valueOf() + this.step * 1000 * 60 * 60); + // in case of skipping an hour for daylight savings, adjust the hour again (else you get: 0h 5h 9h ... instead of 0h 4h 8h ...) + var h = this.current.getHours(); + this.current.setHours(h - (h % this.step)); + break; + case TimeStep.SCALE.WEEKDAY: // intentional fall through + case TimeStep.SCALE.DAY: this.current.setDate(this.current.getDate() + this.step); break; + case TimeStep.SCALE.MONTH: this.current.setMonth(this.current.getMonth() + this.step); break; + case TimeStep.SCALE.YEAR: this.current.setFullYear(this.current.getFullYear() + this.step); break; + default: break; + } + } + else { + switch (this.scale) { + case TimeStep.SCALE.MILLISECOND: this.current = new Date(this.current.valueOf() + this.step); break; + case TimeStep.SCALE.SECOND: this.current.setSeconds(this.current.getSeconds() + this.step); break; + case TimeStep.SCALE.MINUTE: this.current.setMinutes(this.current.getMinutes() + this.step); break; + case TimeStep.SCALE.HOUR: this.current.setHours(this.current.getHours() + this.step); break; + case TimeStep.SCALE.WEEKDAY: // intentional fall through + case TimeStep.SCALE.DAY: this.current.setDate(this.current.getDate() + this.step); break; + case TimeStep.SCALE.MONTH: this.current.setMonth(this.current.getMonth() + this.step); break; + case TimeStep.SCALE.YEAR: this.current.setFullYear(this.current.getFullYear() + this.step); break; + default: break; + } + } + + if (this.step != 1) { + // round down to the correct major value + switch (this.scale) { + case TimeStep.SCALE.MILLISECOND: if(this.current.getMilliseconds() < this.step) this.current.setMilliseconds(0); break; + case TimeStep.SCALE.SECOND: if(this.current.getSeconds() < this.step) this.current.setSeconds(0); break; + case TimeStep.SCALE.MINUTE: if(this.current.getMinutes() < this.step) this.current.setMinutes(0); break; + case TimeStep.SCALE.HOUR: if(this.current.getHours() < this.step) this.current.setHours(0); break; + case TimeStep.SCALE.WEEKDAY: // intentional fall through + case TimeStep.SCALE.DAY: if(this.current.getDate() < this.step+1) this.current.setDate(1); break; + case TimeStep.SCALE.MONTH: if(this.current.getMonth() < this.step) this.current.setMonth(0); break; + case TimeStep.SCALE.YEAR: break; // nothing to do for year + default: break; + } + } + + // safety mechanism: if current time is still unchanged, move to the end + if (this.current.valueOf() == prev) { + this.current = new Date(this._end.valueOf()); + } +}; + + +/** + * Get the current datetime + * @return {Date} current The current date + */ +TimeStep.prototype.getCurrent = function() { + return this.current; +}; + +/** + * Set a custom scale. Autoscaling will be disabled. + * For example setScale(SCALE.MINUTES, 5) will result + * in minor steps of 5 minutes, and major steps of an hour. + * + * @param {TimeStep.SCALE} newScale + * A scale. Choose from SCALE.MILLISECOND, + * SCALE.SECOND, SCALE.MINUTE, SCALE.HOUR, + * SCALE.WEEKDAY, SCALE.DAY, SCALE.MONTH, + * SCALE.YEAR. + * @param {Number} newStep A step size, by default 1. Choose for + * example 1, 2, 5, or 10. + */ +TimeStep.prototype.setScale = function(newScale, newStep) { + this.scale = newScale; + + if (newStep > 0) { + this.step = newStep; + } + + this.autoScale = false; +}; + +/** + * Enable or disable autoscaling + * @param {boolean} enable If true, autoascaling is set true + */ +TimeStep.prototype.setAutoScale = function (enable) { + this.autoScale = enable; +}; + + +/** + * Automatically determine the scale that bests fits the provided minimum step + * @param {Number} [minimumStep] The minimum step size in milliseconds + */ +TimeStep.prototype.setMinimumStep = function(minimumStep) { + if (minimumStep == undefined) { + return; + } + + var stepYear = (1000 * 60 * 60 * 24 * 30 * 12); + var stepMonth = (1000 * 60 * 60 * 24 * 30); + var stepDay = (1000 * 60 * 60 * 24); + var stepHour = (1000 * 60 * 60); + var stepMinute = (1000 * 60); + var stepSecond = (1000); + var stepMillisecond= (1); + + // find the smallest step that is larger than the provided minimumStep + if (stepYear*1000 > minimumStep) {this.scale = TimeStep.SCALE.YEAR; this.step = 1000;} + if (stepYear*500 > minimumStep) {this.scale = TimeStep.SCALE.YEAR; this.step = 500;} + if (stepYear*100 > minimumStep) {this.scale = TimeStep.SCALE.YEAR; this.step = 100;} + if (stepYear*50 > minimumStep) {this.scale = TimeStep.SCALE.YEAR; this.step = 50;} + if (stepYear*10 > minimumStep) {this.scale = TimeStep.SCALE.YEAR; this.step = 10;} + if (stepYear*5 > minimumStep) {this.scale = TimeStep.SCALE.YEAR; this.step = 5;} + if (stepYear > minimumStep) {this.scale = TimeStep.SCALE.YEAR; this.step = 1;} + if (stepMonth*3 > minimumStep) {this.scale = TimeStep.SCALE.MONTH; this.step = 3;} + if (stepMonth > minimumStep) {this.scale = TimeStep.SCALE.MONTH; this.step = 1;} + if (stepDay*5 > minimumStep) {this.scale = TimeStep.SCALE.DAY; this.step = 5;} + if (stepDay*2 > minimumStep) {this.scale = TimeStep.SCALE.DAY; this.step = 2;} + if (stepDay > minimumStep) {this.scale = TimeStep.SCALE.DAY; this.step = 1;} + if (stepDay/2 > minimumStep) {this.scale = TimeStep.SCALE.WEEKDAY; this.step = 1;} + if (stepHour*4 > minimumStep) {this.scale = TimeStep.SCALE.HOUR; this.step = 4;} + if (stepHour > minimumStep) {this.scale = TimeStep.SCALE.HOUR; this.step = 1;} + if (stepMinute*15 > minimumStep) {this.scale = TimeStep.SCALE.MINUTE; this.step = 15;} + if (stepMinute*10 > minimumStep) {this.scale = TimeStep.SCALE.MINUTE; this.step = 10;} + if (stepMinute*5 > minimumStep) {this.scale = TimeStep.SCALE.MINUTE; this.step = 5;} + if (stepMinute > minimumStep) {this.scale = TimeStep.SCALE.MINUTE; this.step = 1;} + if (stepSecond*15 > minimumStep) {this.scale = TimeStep.SCALE.SECOND; this.step = 15;} + if (stepSecond*10 > minimumStep) {this.scale = TimeStep.SCALE.SECOND; this.step = 10;} + if (stepSecond*5 > minimumStep) {this.scale = TimeStep.SCALE.SECOND; this.step = 5;} + if (stepSecond > minimumStep) {this.scale = TimeStep.SCALE.SECOND; this.step = 1;} + if (stepMillisecond*200 > minimumStep) {this.scale = TimeStep.SCALE.MILLISECOND; this.step = 200;} + if (stepMillisecond*100 > minimumStep) {this.scale = TimeStep.SCALE.MILLISECOND; this.step = 100;} + if (stepMillisecond*50 > minimumStep) {this.scale = TimeStep.SCALE.MILLISECOND; this.step = 50;} + if (stepMillisecond*10 > minimumStep) {this.scale = TimeStep.SCALE.MILLISECOND; this.step = 10;} + if (stepMillisecond*5 > minimumStep) {this.scale = TimeStep.SCALE.MILLISECOND; this.step = 5;} + if (stepMillisecond > minimumStep) {this.scale = TimeStep.SCALE.MILLISECOND; this.step = 1;} +}; + +/** + * Snap a date to a rounded value. + * The snap intervals are dependent on the current scale and step. + * @param {Date} date the date to be snapped. + * @return {Date} snappedDate + */ +TimeStep.prototype.snap = function(date) { + var clone = new Date(date.valueOf()); + + if (this.scale == TimeStep.SCALE.YEAR) { + var year = clone.getFullYear() + Math.round(clone.getMonth() / 12); + clone.setFullYear(Math.round(year / this.step) * this.step); + clone.setMonth(0); + clone.setDate(0); + clone.setHours(0); + clone.setMinutes(0); + clone.setSeconds(0); + clone.setMilliseconds(0); + } + else if (this.scale == TimeStep.SCALE.MONTH) { + if (clone.getDate() > 15) { + clone.setDate(1); + clone.setMonth(clone.getMonth() + 1); + // important: first set Date to 1, after that change the month. + } + else { + clone.setDate(1); + } + + clone.setHours(0); + clone.setMinutes(0); + clone.setSeconds(0); + clone.setMilliseconds(0); + } + else if (this.scale == TimeStep.SCALE.DAY || + this.scale == TimeStep.SCALE.WEEKDAY) { + //noinspection FallthroughInSwitchStatementJS + switch (this.step) { + case 5: + case 2: + clone.setHours(Math.round(clone.getHours() / 24) * 24); break; + default: + clone.setHours(Math.round(clone.getHours() / 12) * 12); break; + } + clone.setMinutes(0); + clone.setSeconds(0); + clone.setMilliseconds(0); + } + else if (this.scale == TimeStep.SCALE.HOUR) { + switch (this.step) { + case 4: + clone.setMinutes(Math.round(clone.getMinutes() / 60) * 60); break; + default: + clone.setMinutes(Math.round(clone.getMinutes() / 30) * 30); break; + } + clone.setSeconds(0); + clone.setMilliseconds(0); + } else if (this.scale == TimeStep.SCALE.MINUTE) { + //noinspection FallthroughInSwitchStatementJS + switch (this.step) { + case 15: + case 10: + clone.setMinutes(Math.round(clone.getMinutes() / 5) * 5); + clone.setSeconds(0); + break; + case 5: + clone.setSeconds(Math.round(clone.getSeconds() / 60) * 60); break; + default: + clone.setSeconds(Math.round(clone.getSeconds() / 30) * 30); break; + } + clone.setMilliseconds(0); + } + else if (this.scale == TimeStep.SCALE.SECOND) { + //noinspection FallthroughInSwitchStatementJS + switch (this.step) { + case 15: + case 10: + clone.setSeconds(Math.round(clone.getSeconds() / 5) * 5); + clone.setMilliseconds(0); + break; + case 5: + clone.setMilliseconds(Math.round(clone.getMilliseconds() / 1000) * 1000); break; + default: + clone.setMilliseconds(Math.round(clone.getMilliseconds() / 500) * 500); break; + } + } + else if (this.scale == TimeStep.SCALE.MILLISECOND) { + var step = this.step > 5 ? this.step / 2 : 1; + clone.setMilliseconds(Math.round(clone.getMilliseconds() / step) * step); + } + + return clone; +}; + +/** + * Check if the current value is a major value (for example when the step + * is DAY, a major value is each first day of the MONTH) + * @return {boolean} true if current date is major, else false. + */ +TimeStep.prototype.isMajor = function() { + switch (this.scale) { + case TimeStep.SCALE.MILLISECOND: + return (this.current.getMilliseconds() == 0); + case TimeStep.SCALE.SECOND: + return (this.current.getSeconds() == 0); + case TimeStep.SCALE.MINUTE: + return (this.current.getHours() == 0) && (this.current.getMinutes() == 0); + // Note: this is no bug. Major label is equal for both minute and hour scale + case TimeStep.SCALE.HOUR: + return (this.current.getHours() == 0); + case TimeStep.SCALE.WEEKDAY: // intentional fall through + case TimeStep.SCALE.DAY: + return (this.current.getDate() == 1); + case TimeStep.SCALE.MONTH: + return (this.current.getMonth() == 0); + case TimeStep.SCALE.YEAR: + return false; + default: + return false; + } +}; + + +/** + * Returns formatted text for the minor axislabel, depending on the current + * date and the scale. For example when scale is MINUTE, the current time is + * formatted as "hh:mm". + * @param {Date} [date] custom date. if not provided, current date is taken + */ +TimeStep.prototype.getLabelMinor = function(date) { + if (date == undefined) { + date = this.current; + } + + switch (this.scale) { + case TimeStep.SCALE.MILLISECOND: return moment(date).format('SSS'); + case TimeStep.SCALE.SECOND: return moment(date).format('s'); + case TimeStep.SCALE.MINUTE: return moment(date).format('HH:mm'); + case TimeStep.SCALE.HOUR: return moment(date).format('HH:mm'); + case TimeStep.SCALE.WEEKDAY: return moment(date).format('ddd D'); + case TimeStep.SCALE.DAY: return moment(date).format('D'); + case TimeStep.SCALE.MONTH: return moment(date).format('MMM'); + case TimeStep.SCALE.YEAR: return moment(date).format('YYYY'); + default: return ''; + } +}; + + +/** + * Returns formatted text for the major axis label, depending on the current + * date and the scale. For example when scale is MINUTE, the major scale is + * hours, and the hour will be formatted as "hh". + * @param {Date} [date] custom date. if not provided, current date is taken + */ +TimeStep.prototype.getLabelMajor = function(date) { + if (date == undefined) { + date = this.current; + } + + //noinspection FallthroughInSwitchStatementJS + switch (this.scale) { + case TimeStep.SCALE.MILLISECOND:return moment(date).format('HH:mm:ss'); + case TimeStep.SCALE.SECOND: return moment(date).format('D MMMM HH:mm'); + case TimeStep.SCALE.MINUTE: + case TimeStep.SCALE.HOUR: return moment(date).format('ddd D MMMM'); + case TimeStep.SCALE.WEEKDAY: + case TimeStep.SCALE.DAY: return moment(date).format('MMMM YYYY'); + case TimeStep.SCALE.MONTH: return moment(date).format('YYYY'); + case TimeStep.SCALE.YEAR: return ''; + default: return ''; + } +}; + +/** + * @constructor Stack + * Stacks items on top of each other. + * @param {ItemSet} itemset + * @param {Object} [options] + */ +function Stack (itemset, options) { + this.itemset = itemset; + + this.options = options || {}; + this.defaultOptions = { + order: function (a, b) { + //return (b.width - a.width) || (a.left - b.left); // TODO: cleanup + // Order: ranges over non-ranges, ranged ordered by width, and + // lastly ordered by start. + if (a instanceof ItemRange) { + if (b instanceof ItemRange) { + var aInt = (a.data.end - a.data.start); + var bInt = (b.data.end - b.data.start); + return (aInt - bInt) || (a.data.start - b.data.start); + } + else { + return -1; + } + } + else { + if (b instanceof ItemRange) { + return 1; + } + else { + return (a.data.start - b.data.start); + } + } + }, + margin: { + item: 10 + } + }; + + this.ordered = []; // ordered items +} + +/** + * Set options for the stack + * @param {Object} options Available options: + * {ItemSet} itemset + * {Number} margin + * {function} order Stacking order + */ +Stack.prototype.setOptions = function setOptions (options) { + util.extend(this.options, options); + + // TODO: register on data changes at the connected itemset, and update the changed part only and immediately +}; + +/** + * Stack the items such that they don't overlap. The items will have a minimal + * distance equal to options.margin.item. + */ +Stack.prototype.update = function update() { + this._order(); + this._stack(); +}; + +/** + * Order the items. If a custom order function has been provided via the options, + * then this will be used. + * @private + */ +Stack.prototype._order = function _order () { + var items = this.itemset.items; + if (!items) { + throw new Error('Cannot stack items: ItemSet does not contain items'); + } + + // TODO: store the sorted items, to have less work later on + var ordered = []; + var index = 0; + // items is a map (no array) + util.forEach(items, function (item) { + if (item.visible) { + ordered[index] = item; + index++; + } + }); + + //if a customer stack order function exists, use it. + var order = this.options.order || this.defaultOptions.order; + if (!(typeof order === 'function')) { + throw new Error('Option order must be a function'); + } + + ordered.sort(order); + + this.ordered = ordered; +}; + +/** + * Adjust vertical positions of the events such that they don't overlap each + * other. + * @private + */ +Stack.prototype._stack = function _stack () { + var i, + iMax, + ordered = this.ordered, + options = this.options, + orientation = options.orientation || this.defaultOptions.orientation, + axisOnTop = (orientation == 'top'), + margin; + + if (options.margin && options.margin.item !== undefined) { + margin = options.margin.item; + } + else { + margin = this.defaultOptions.margin.item + } + + // calculate new, non-overlapping positions + for (i = 0, iMax = ordered.length; i < iMax; i++) { + var item = ordered[i]; + var collidingItem = null; + do { + // TODO: optimize checking for overlap. when there is a gap without items, + // you only need to check for items from the next item on, not from zero + collidingItem = this.checkOverlap(ordered, i, 0, i - 1, margin); + if (collidingItem != null) { + // There is a collision. Reposition the event above the colliding element + if (axisOnTop) { + item.top = collidingItem.top + collidingItem.height + margin; + } + else { + item.top = collidingItem.top - item.height - margin; + } + } + } while (collidingItem); + } +}; + +/** + * Check if the destiny position of given item overlaps with any + * of the other items from index itemStart to itemEnd. + * @param {Array} items Array with items + * @param {int} itemIndex Number of the item to be checked for overlap + * @param {int} itemStart First item to be checked. + * @param {int} itemEnd Last item to be checked. + * @return {Object | null} colliding item, or undefined when no collisions + * @param {Number} margin A minimum required margin. + * If margin is provided, the two items will be + * marked colliding when they overlap or + * when the margin between the two is smaller than + * the requested margin. + */ +Stack.prototype.checkOverlap = function checkOverlap (items, itemIndex, + itemStart, itemEnd, margin) { + var collision = this.collision; + + // we loop from end to start, as we suppose that the chance of a + // collision is larger for items at the end, so check these first. + var a = items[itemIndex]; + for (var i = itemEnd; i >= itemStart; i--) { + var b = items[i]; + if (collision(a, b, margin)) { + if (i != itemIndex) { + return b; + } + } + } + + return null; +}; + +/** + * Test if the two provided items collide + * The items must have parameters left, width, top, and height. + * @param {Component} a The first item + * @param {Component} b The second item + * @param {Number} margin A minimum required margin. + * If margin is provided, the two items will be + * marked colliding when they overlap or + * when the margin between the two is smaller than + * the requested margin. + * @return {boolean} true if a and b collide, else false + */ +Stack.prototype.collision = function collision (a, b, margin) { + return ((a.left - margin) < (b.left + b.width) && + (a.left + a.width + margin) > b.left && + (a.top - margin) < (b.top + b.height) && + (a.top + a.height + margin) > b.top); +}; + +/** + * @constructor Range + * A Range controls a numeric range with a start and end value. + * The Range adjusts the range based on mouse events or programmatic changes, + * and triggers events when the range is changing or has been changed. + * @param {Object} [options] See description at Range.setOptions + * @extends Controller + */ +function Range(options) { + this.id = util.randomUUID(); + this.start = null; // Number + this.end = null; // Number + + this.options = options || {}; + + this.setOptions(options); +} + +// extend the Range prototype with an event emitter mixin +Emitter(Range.prototype); + +/** + * Set options for the range controller + * @param {Object} options Available options: + * {Number} min Minimum value for start + * {Number} max Maximum value for end + * {Number} zoomMin Set a minimum value for + * (end - start). + * {Number} zoomMax Set a maximum value for + * (end - start). + */ +Range.prototype.setOptions = function (options) { + util.extend(this.options, options); + + // re-apply range with new limitations + if (this.start !== null && this.end !== null) { + this.setRange(this.start, this.end); + } +}; + +/** + * Test whether direction has a valid value + * @param {String} direction 'horizontal' or 'vertical' + */ +function validateDirection (direction) { + if (direction != 'horizontal' && direction != 'vertical') { + throw new TypeError('Unknown direction "' + direction + '". ' + + 'Choose "horizontal" or "vertical".'); + } +} + +/** + * Add listeners for mouse and touch events to the component + * @param {Controller} controller + * @param {Component} component Should be a rootpanel + * @param {String} event Available events: 'move', 'zoom' + * @param {String} direction Available directions: 'horizontal', 'vertical' + */ +Range.prototype.subscribe = function (controller, component, event, direction) { + var me = this; + + if (event == 'move') { + // drag start listener + controller.on('dragstart', function (event) { + me._onDragStart(event, component); + }); + + // drag listener + controller.on('drag', function (event) { + me._onDrag(event, component, direction); + }); + + // drag end listener + controller.on('dragend', function (event) { + me._onDragEnd(event, component); + }); + + // ignore dragging when holding + controller.on('hold', function (event) { + me._onHold(); + }); + } + else if (event == 'zoom') { + // mouse wheel + function mousewheel (event) { + me._onMouseWheel(event, component, direction); + } + controller.on('mousewheel', mousewheel); + controller.on('DOMMouseScroll', mousewheel); // For FF + + // pinch + controller.on('touch', function (event) { + me._onTouch(event); + }); + controller.on('pinch', function (event) { + me._onPinch(event, component, direction); + }); + } + else { + throw new TypeError('Unknown event "' + event + '". ' + + 'Choose "move" or "zoom".'); + } +}; + +/** + * Set a new start and end range + * @param {Number} [start] + * @param {Number} [end] + */ +Range.prototype.setRange = function(start, end) { + var changed = this._applyRange(start, end); + if (changed) { + var params = { + start: this.start, + end: this.end + }; + this.emit('rangechange', params); + this.emit('rangechanged', params); + } +}; + +/** + * Set a new start and end range. This method is the same as setRange, but + * does not trigger a range change and range changed event, and it returns + * true when the range is changed + * @param {Number} [start] + * @param {Number} [end] + * @return {Boolean} changed + * @private + */ +Range.prototype._applyRange = function(start, end) { + var newStart = (start != null) ? util.convert(start, 'Date').valueOf() : this.start, + newEnd = (end != null) ? util.convert(end, 'Date').valueOf() : this.end, + max = (this.options.max != null) ? util.convert(this.options.max, 'Date').valueOf() : null, + min = (this.options.min != null) ? util.convert(this.options.min, 'Date').valueOf() : null, + diff; + + // check for valid number + if (isNaN(newStart) || newStart === null) { + throw new Error('Invalid start "' + start + '"'); + } + if (isNaN(newEnd) || newEnd === null) { + throw new Error('Invalid end "' + end + '"'); + } + + // prevent start < end + if (newEnd < newStart) { + newEnd = newStart; + } + + // prevent start < min + if (min !== null) { + if (newStart < min) { + diff = (min - newStart); + newStart += diff; + newEnd += diff; + + // prevent end > max + if (max != null) { + if (newEnd > max) { + newEnd = max; + } + } + } + } + + // prevent end > max + if (max !== null) { + if (newEnd > max) { + diff = (newEnd - max); + newStart -= diff; + newEnd -= diff; + + // prevent start < min + if (min != null) { + if (newStart < min) { + newStart = min; + } + } + } + } + + // prevent (end-start) < zoomMin + if (this.options.zoomMin !== null) { + var zoomMin = parseFloat(this.options.zoomMin); + if (zoomMin < 0) { + zoomMin = 0; + } + if ((newEnd - newStart) < zoomMin) { + if ((this.end - this.start) === zoomMin) { + // ignore this action, we are already zoomed to the minimum + newStart = this.start; + newEnd = this.end; + } + else { + // zoom to the minimum + diff = (zoomMin - (newEnd - newStart)); + newStart -= diff / 2; + newEnd += diff / 2; + } + } + } + + // prevent (end-start) > zoomMax + if (this.options.zoomMax !== null) { + var zoomMax = parseFloat(this.options.zoomMax); + if (zoomMax < 0) { + zoomMax = 0; + } + if ((newEnd - newStart) > zoomMax) { + if ((this.end - this.start) === zoomMax) { + // ignore this action, we are already zoomed to the maximum + newStart = this.start; + newEnd = this.end; + } + else { + // zoom to the maximum + diff = ((newEnd - newStart) - zoomMax); + newStart += diff / 2; + newEnd -= diff / 2; + } + } + } + + var changed = (this.start != newStart || this.end != newEnd); + + this.start = newStart; + this.end = newEnd; + + return changed; +}; + +/** + * Retrieve the current range. + * @return {Object} An object with start and end properties + */ +Range.prototype.getRange = function() { + return { + start: this.start, + end: this.end + }; +}; + +/** + * Calculate the conversion offset and scale for current range, based on + * the provided width + * @param {Number} width + * @returns {{offset: number, scale: number}} conversion + */ +Range.prototype.conversion = function (width) { + return Range.conversion(this.start, this.end, width); +}; + +/** + * Static method to calculate the conversion offset and scale for a range, + * based on the provided start, end, and width + * @param {Number} start + * @param {Number} end + * @param {Number} width + * @returns {{offset: number, scale: number}} conversion + */ +Range.conversion = function (start, end, width) { + if (width != 0 && (end - start != 0)) { + return { + offset: start, + scale: width / (end - start) + } + } + else { + return { + offset: 0, + scale: 1 + }; + } +}; + +// global (private) object to store drag params +var touchParams = {}; + +/** + * Start dragging horizontally or vertically + * @param {Event} event + * @param {Object} component + * @private + */ +Range.prototype._onDragStart = function(event, component) { + // refuse to drag when we where pinching to prevent the timeline make a jump + // when releasing the fingers in opposite order from the touch screen + if (touchParams.ignore) return; + + // TODO: reckon with option movable + + touchParams.start = this.start; + touchParams.end = this.end; + + var frame = component.frame; + if (frame) { + frame.style.cursor = 'move'; + } +}; + +/** + * Perform dragging operating. + * @param {Event} event + * @param {Component} component + * @param {String} direction 'horizontal' or 'vertical' + * @private + */ +Range.prototype._onDrag = function (event, component, direction) { + validateDirection(direction); + + // TODO: reckon with option movable + + + // refuse to drag when we where pinching to prevent the timeline make a jump + // when releasing the fingers in opposite order from the touch screen + if (touchParams.ignore) return; + + var delta = (direction == 'horizontal') ? event.gesture.deltaX : event.gesture.deltaY, + interval = (touchParams.end - touchParams.start), + width = (direction == 'horizontal') ? component.width : component.height, + diffRange = -delta / width * interval; + + this._applyRange(touchParams.start + diffRange, touchParams.end + diffRange); + + this.emit('rangechange', { + start: this.start, + end: this.end + }); +}; + +/** + * Stop dragging operating. + * @param {event} event + * @param {Component} component + * @private + */ +Range.prototype._onDragEnd = function (event, component) { + // refuse to drag when we where pinching to prevent the timeline make a jump + // when releasing the fingers in opposite order from the touch screen + if (touchParams.ignore) return; + + // TODO: reckon with option movable + + if (component.frame) { + component.frame.style.cursor = 'auto'; + } + + // fire a rangechanged event + this.emit('rangechanged', { + start: this.start, + end: this.end + }); +}; + +/** + * Event handler for mouse wheel event, used to zoom + * Code from http://adomas.org/javascript-mouse-wheel/ + * @param {Event} event + * @param {Component} component + * @param {String} direction 'horizontal' or 'vertical' + * @private + */ +Range.prototype._onMouseWheel = function(event, component, direction) { + validateDirection(direction); + + // TODO: reckon with option zoomable + + // retrieve delta + var delta = 0; + if (event.wheelDelta) { /* IE/Opera. */ + delta = event.wheelDelta / 120; + } else if (event.detail) { /* Mozilla case. */ + // In Mozilla, sign of delta is different than in IE. + // Also, delta is multiple of 3. + delta = -event.detail / 3; + } + + // If delta is nonzero, handle it. + // Basically, delta is now positive if wheel was scrolled up, + // and negative, if wheel was scrolled down. + if (delta) { + // perform the zoom action. Delta is normally 1 or -1 + + // adjust a negative delta such that zooming in with delta 0.1 + // equals zooming out with a delta -0.1 + var scale; + if (delta < 0) { + scale = 1 - (delta / 5); + } + else { + scale = 1 / (1 + (delta / 5)) ; + } + + // calculate center, the date to zoom around + var gesture = util.fakeGesture(this, event), + pointer = getPointer(gesture.center, component.frame), + pointerDate = this._pointerToDate(component, direction, pointer); + + this.zoom(scale, pointerDate); + } + + // Prevent default actions caused by mouse wheel + // (else the page and timeline both zoom and scroll) + event.preventDefault(); +}; + +/** + * Start of a touch gesture + * @private + */ +Range.prototype._onTouch = function (event) { + touchParams.start = this.start; + touchParams.end = this.end; + touchParams.ignore = false; + touchParams.center = null; + + // don't move the range when dragging a selected event + // TODO: it's not so neat to have to know about the state of the ItemSet + var item = ItemSet.itemFromTarget(event); + if (item && item.selected && this.options.editable) { + touchParams.ignore = true; + } +}; + +/** + * On start of a hold gesture + * @private + */ +Range.prototype._onHold = function () { + touchParams.ignore = true; +}; + +/** + * Handle pinch event + * @param {Event} event + * @param {Component} component + * @param {String} direction 'horizontal' or 'vertical' + * @private + */ +Range.prototype._onPinch = function (event, component, direction) { + touchParams.ignore = true; + + // TODO: reckon with option zoomable + + if (event.gesture.touches.length > 1) { + if (!touchParams.center) { + touchParams.center = getPointer(event.gesture.center, component.frame); + } + + var scale = 1 / event.gesture.scale, + initDate = this._pointerToDate(component, direction, touchParams.center), + center = getPointer(event.gesture.center, component.frame), + date = this._pointerToDate(component, direction, center), + delta = date - initDate; // TODO: utilize delta + + // calculate new start and end + var newStart = parseInt(initDate + (touchParams.start - initDate) * scale); + var newEnd = parseInt(initDate + (touchParams.end - initDate) * scale); + + // apply new range + this.setRange(newStart, newEnd); + } +}; + +/** + * Helper function to calculate the center date for zooming + * @param {Component} component + * @param {{x: Number, y: Number}} pointer + * @param {String} direction 'horizontal' or 'vertical' + * @return {number} date + * @private + */ +Range.prototype._pointerToDate = function (component, direction, pointer) { + var conversion; + if (direction == 'horizontal') { + var width = component.width; + conversion = this.conversion(width); + return pointer.x / conversion.scale + conversion.offset; + } + else { + var height = component.height; + conversion = this.conversion(height); + return pointer.y / conversion.scale + conversion.offset; + } +}; + +/** + * Get the pointer location relative to the location of the dom element + * @param {{pageX: Number, pageY: Number}} touch + * @param {Element} element HTML DOM element + * @return {{x: Number, y: Number}} pointer + * @private + */ +function getPointer (touch, element) { + return { + x: touch.pageX - vis.util.getAbsoluteLeft(element), + y: touch.pageY - vis.util.getAbsoluteTop(element) + }; +} + +/** + * Zoom the range the given scale in or out. Start and end date will + * be adjusted, and the timeline will be redrawn. You can optionally give a + * date around which to zoom. + * For example, try scale = 0.9 or 1.1 + * @param {Number} scale Scaling factor. Values above 1 will zoom out, + * values below 1 will zoom in. + * @param {Number} [center] Value representing a date around which will + * be zoomed. + */ +Range.prototype.zoom = function(scale, center) { + // if centerDate is not provided, take it half between start Date and end Date + if (center == null) { + center = (this.start + this.end) / 2; + } + + // calculate new start and end + var newStart = center + (this.start - center) * scale; + var newEnd = center + (this.end - center) * scale; + + this.setRange(newStart, newEnd); +}; + +/** + * Move the range with a given delta to the left or right. Start and end + * value will be adjusted. For example, try delta = 0.1 or -0.1 + * @param {Number} delta Moving amount. Positive value will move right, + * negative value will move left + */ +Range.prototype.move = function(delta) { + // zoom start Date and end Date relative to the centerDate + var diff = (this.end - this.start); + + // apply new values + var newStart = this.start + diff * delta; + var newEnd = this.end + diff * delta; + + // TODO: reckon with min and max range + + this.start = newStart; + this.end = newEnd; +}; + +/** + * Move the range to a new center point + * @param {Number} moveTo New center point of the range + */ +Range.prototype.moveTo = function(moveTo) { + var center = (this.start + this.end) / 2; + + var diff = center - moveTo; + + // calculate new start and end + var newStart = this.start - diff; + var newEnd = this.end - diff; + + this.setRange(newStart, newEnd); +}; + +/** + * @constructor Controller + * + * A Controller controls the reflows and repaints of all components, + * and is used as an event bus for all components. + */ +function Controller () { + var me = this; + + this.id = util.randomUUID(); + this.components = {}; + + /** + * Listen for a 'request-reflow' event. The controller will schedule a reflow + * @param {Boolean} [force] If true, an immediate reflow is forced. Default + * is false. + */ + var reflowTimer = null; + this.on('request-reflow', function requestReflow(force) { + if (force) { + me.reflow(); + } + else { + if (!reflowTimer) { + reflowTimer = setTimeout(function () { + reflowTimer = null; + me.reflow(); + }, 0); + } + } + }); + + /** + * Request a repaint. The controller will schedule a repaint + * @param {Boolean} [force] If true, an immediate repaint is forced. Default + * is false. + */ + var repaintTimer = null; + this.on('request-repaint', function requestRepaint(force) { + if (force) { + me.repaint(); + } + else { + if (!repaintTimer) { + repaintTimer = setTimeout(function () { + repaintTimer = null; + me.repaint(); + }, 0); + } + } + }); +} + +// Extend controller with Emitter mixin +Emitter(Controller.prototype); + +/** + * Add a component to the controller + * @param {Component} component + */ +Controller.prototype.add = function add(component) { + // validate the component + if (component.id == undefined) { + throw new Error('Component has no field id'); + } + if (!(component instanceof Component) && !(component instanceof Controller)) { + throw new TypeError('Component must be an instance of ' + + 'prototype Component or Controller'); + } + + // add the component + component.setController(this); + this.components[component.id] = component; +}; + +/** + * Remove a component from the controller + * @param {Component | String} component + */ +Controller.prototype.remove = function remove(component) { + var id; + for (id in this.components) { + if (this.components.hasOwnProperty(id)) { + if (id == component || this.components[id] === component) { + break; + } + } + } + + if (id) { + // unregister the controller (gives the component the ability to unregister + // event listeners and clean up other stuff) + this.components[id].setController(null); + + delete this.components[id]; + } +}; + +/** + * Repaint all components + */ +Controller.prototype.repaint = function repaint() { + var changed = false; + + // cancel any running repaint request + if (this.repaintTimer) { + clearTimeout(this.repaintTimer); + this.repaintTimer = undefined; + } + + var done = {}; + + function repaint(component, id) { + if (!(id in done)) { + // first repaint the components on which this component is dependent + if (component.depends) { + component.depends.forEach(function (dep) { + repaint(dep, dep.id); + }); + } + if (component.parent) { + repaint(component.parent, component.parent.id); + } + + // repaint the component itself and mark as done + changed = component.repaint() || changed; + done[id] = true; + } + } + + util.forEach(this.components, repaint); + + this.emit('repaint'); + + // immediately reflow when needed + if (changed) { + this.reflow(); + } + // TODO: limit the number of nested reflows/repaints, prevent loop +}; + +/** + * Reflow all components + */ +Controller.prototype.reflow = function reflow() { + var resized = false; + + // cancel any running repaint request + if (this.reflowTimer) { + clearTimeout(this.reflowTimer); + this.reflowTimer = undefined; + } + + var done = {}; + + function reflow(component, id) { + if (!(id in done)) { + // first reflow the components on which this component is dependent + if (component.depends) { + component.depends.forEach(function (dep) { + reflow(dep, dep.id); + }); + } + if (component.parent) { + reflow(component.parent, component.parent.id); + } + + // reflow the component itself and mark as done + resized = component.reflow() || resized; + done[id] = true; + } + } + + util.forEach(this.components, reflow); + + this.emit('reflow'); + + // immediately repaint when needed + if (resized) { + this.repaint(); + } + // TODO: limit the number of nested reflows/repaints, prevent loop +}; + +/** + * Prototype for visual components + */ +function Component () { + this.id = null; + this.parent = null; + this.depends = null; + this.controller = null; + this.options = null; + + this.frame = null; // main DOM element + this.top = 0; + this.left = 0; + this.width = 0; + this.height = 0; +} + +/** + * Set parameters for the frame. Parameters will be merged in current parameter + * set. + * @param {Object} options Available parameters: + * {String | function} [className] + * {String | Number | function} [left] + * {String | Number | function} [top] + * {String | Number | function} [width] + * {String | Number | function} [height] + */ +Component.prototype.setOptions = function setOptions(options) { + if (options) { + util.extend(this.options, options); + + if (this.controller) { + this.requestRepaint(); + this.requestReflow(); + } + } +}; + +/** + * Get an option value by name + * The function will first check this.options object, and else will check + * this.defaultOptions. + * @param {String} name + * @return {*} value + */ +Component.prototype.getOption = function getOption(name) { + var value; + if (this.options) { + value = this.options[name]; + } + if (value === undefined && this.defaultOptions) { + value = this.defaultOptions[name]; + } + return value; +}; + +/** + * Set controller for this component, or remove current controller by passing + * null as parameter value. + * @param {Controller | null} controller + */ +Component.prototype.setController = function setController (controller) { + this.controller = controller || null; +}; + +/** + * Get controller of this component + * @return {Controller} controller + */ +Component.prototype.getController = function getController () { + return this.controller; +}; + +/** + * Get the container element of the component, which can be used by a child to + * add its own widgets. Not all components do have a container for childs, in + * that case null is returned. + * @returns {HTMLElement | null} container + */ +// TODO: get rid of the getContainer and getFrame methods, provide these via the options +Component.prototype.getContainer = function getContainer() { + // should be implemented by the component + return null; +}; + +/** + * Get the frame element of the component, the outer HTML DOM element. + * @returns {HTMLElement | null} frame + */ +Component.prototype.getFrame = function getFrame() { + return this.frame; +}; + +/** + * Repaint the component + * @return {Boolean} changed + */ +Component.prototype.repaint = function repaint() { + // should be implemented by the component + return false; +}; + +/** + * Reflow the component + * @return {Boolean} resized + */ +Component.prototype.reflow = function reflow() { + // should be implemented by the component + return false; +}; + +/** + * Hide the component from the DOM + * @return {Boolean} changed + */ +Component.prototype.hide = function hide() { + if (this.frame && this.frame.parentNode) { + this.frame.parentNode.removeChild(this.frame); + return true; + } + else { + return false; + } +}; + +/** + * Show the component in the DOM (when not already visible). + * A repaint will be executed when the component is not visible + * @return {Boolean} changed + */ +Component.prototype.show = function show() { + if (!this.frame || !this.frame.parentNode) { + return this.repaint(); + } + else { + return false; + } +}; + +/** + * Request a repaint. The controller will schedule a repaint + */ +Component.prototype.requestRepaint = function requestRepaint() { + if (this.controller) { + this.controller.emit('request-repaint'); + } + else { + throw new Error('Cannot request a repaint: no controller configured'); + // TODO: just do a repaint when no parent is configured? + } +}; + +/** + * Request a reflow. The controller will schedule a reflow + */ +Component.prototype.requestReflow = function requestReflow() { + if (this.controller) { + this.controller.emit('request-reflow'); + } + else { + throw new Error('Cannot request a reflow: no controller configured'); + // TODO: just do a reflow when no parent is configured? + } +}; + +/** + * A panel can contain components + * @param {Component} [parent] + * @param {Component[]} [depends] Components on which this components depends + * (except for the parent) + * @param {Object} [options] Available parameters: + * {String | Number | function} [left] + * {String | Number | function} [top] + * {String | Number | function} [width] + * {String | Number | function} [height] + * {String | function} [className] + * @constructor Panel + * @extends Component + */ +function Panel(parent, depends, options) { + this.id = util.randomUUID(); + this.parent = parent; + this.depends = depends; + + this.options = options || {}; +} + +Panel.prototype = new Component(); + +/** + * Set options. Will extend the current options. + * @param {Object} [options] Available parameters: + * {String | function} [className] + * {String | Number | function} [left] + * {String | Number | function} [top] + * {String | Number | function} [width] + * {String | Number | function} [height] + */ +Panel.prototype.setOptions = Component.prototype.setOptions; + +/** + * Get the container element of the panel, which can be used by a child to + * add its own widgets. + * @returns {HTMLElement} container + */ +Panel.prototype.getContainer = function () { + return this.frame; +}; + +/** + * Repaint the component + * @return {Boolean} changed + */ +Panel.prototype.repaint = function () { + var changed = 0, + update = util.updateProperty, + asSize = util.option.asSize, + options = this.options, + frame = this.frame; + if (!frame) { + frame = document.createElement('div'); + frame.className = 'vpanel'; + + var className = options.className; + if (className) { + if (typeof className == 'function') { + util.addClassName(frame, String(className())); + } + else { + util.addClassName(frame, String(className)); + } + } + + this.frame = frame; + changed += 1; + } + if (!frame.parentNode) { + if (!this.parent) { + throw new Error('Cannot repaint panel: no parent attached'); + } + var parentContainer = this.parent.getContainer(); + if (!parentContainer) { + throw new Error('Cannot repaint panel: parent has no container element'); + } + parentContainer.appendChild(frame); + changed += 1; + } + + changed += update(frame.style, 'top', asSize(options.top, '0px')); + changed += update(frame.style, 'left', asSize(options.left, '0px')); + changed += update(frame.style, 'width', asSize(options.width, '100%')); + changed += update(frame.style, 'height', asSize(options.height, '100%')); + + return (changed > 0); +}; + +/** + * Reflow the component + * @return {Boolean} resized + */ +Panel.prototype.reflow = function () { + var changed = 0, + update = util.updateProperty, + frame = this.frame; + + if (frame) { + changed += update(this, 'top', frame.offsetTop); + changed += update(this, 'left', frame.offsetLeft); + changed += update(this, 'width', frame.offsetWidth); + changed += update(this, 'height', frame.offsetHeight); + } + else { + changed += 1; + } + + return (changed > 0); +}; + +/** + * A root panel can hold components. The root panel must be initialized with + * a DOM element as container. + * @param {HTMLElement} container + * @param {Object} [options] Available parameters: see RootPanel.setOptions. + * @constructor RootPanel + * @extends Panel + */ +function RootPanel(container, options) { + this.id = util.randomUUID(); + this.container = container; + + // create functions to be used as DOM event listeners + var me = this; + this.hammer = null; + + // create listeners for all interesting events, these events will be emitted + // via the controller + var events = [ + 'touch', 'pinch', 'tap', 'doubletap', 'hold', + 'dragstart', 'drag', 'dragend', + 'mousewheel', 'DOMMouseScroll' // DOMMouseScroll is for Firefox + ]; + this.listeners = {}; + events.forEach(function (event) { + me.listeners[event] = function () { + var args = [event].concat(Array.prototype.slice.call(arguments, 0)); + me.controller.emit.apply(me.controller, args); + }; + }); + + this.options = options || {}; + this.defaultOptions = { + autoResize: true + }; +} + +RootPanel.prototype = new Panel(); + +/** + * Set options. Will extend the current options. + * @param {Object} [options] Available parameters: + * {String | function} [className] + * {String | Number | function} [left] + * {String | Number | function} [top] + * {String | Number | function} [width] + * {String | Number | function} [height] + * {Boolean | function} [autoResize] + */ +RootPanel.prototype.setOptions = Component.prototype.setOptions; + +/** + * Repaint the component + * @return {Boolean} changed + */ +RootPanel.prototype.repaint = function () { + var changed = 0, + update = util.updateProperty, + asSize = util.option.asSize, + options = this.options, + frame = this.frame; + + if (!frame) { + frame = document.createElement('div'); + + this.frame = frame; + + this._registerListeners(); + + changed += 1; + } + if (!frame.parentNode) { + if (!this.container) { + throw new Error('Cannot repaint root panel: no container attached'); + } + this.container.appendChild(frame); + changed += 1; + } + + frame.className = 'vis timeline rootpanel ' + options.orientation + + (options.editable ? ' editable' : ''); + var className = options.className; + if (className) { + util.addClassName(frame, util.option.asString(className)); + } + + changed += update(frame.style, 'top', asSize(options.top, '0px')); + changed += update(frame.style, 'left', asSize(options.left, '0px')); + changed += update(frame.style, 'width', asSize(options.width, '100%')); + changed += update(frame.style, 'height', asSize(options.height, '100%')); + + this._updateWatch(); + + return (changed > 0); +}; + +/** + * Reflow the component + * @return {Boolean} resized + */ +RootPanel.prototype.reflow = function () { + var changed = 0, + update = util.updateProperty, + frame = this.frame; + + if (frame) { + changed += update(this, 'top', frame.offsetTop); + changed += update(this, 'left', frame.offsetLeft); + changed += update(this, 'width', frame.offsetWidth); + changed += update(this, 'height', frame.offsetHeight); + } + else { + changed += 1; + } + + return (changed > 0); +}; + +/** + * Update watching for resize, depending on the current option + * @private + */ +RootPanel.prototype._updateWatch = function () { + var autoResize = this.getOption('autoResize'); + if (autoResize) { + this._watch(); + } + else { + this._unwatch(); + } +}; + +/** + * Watch for changes in the size of the frame. On resize, the Panel will + * automatically redraw itself. + * @private + */ +RootPanel.prototype._watch = function () { + var me = this; + + this._unwatch(); + + var checkSize = function () { + var autoResize = me.getOption('autoResize'); + if (!autoResize) { + // stop watching when the option autoResize is changed to false + me._unwatch(); + return; + } + + if (me.frame) { + // check whether the frame is resized + if ((me.frame.clientWidth != me.width) || + (me.frame.clientHeight != me.height)) { + me.requestReflow(); + } + } + }; + + // TODO: automatically cleanup the event listener when the frame is deleted + util.addEventListener(window, 'resize', checkSize); + + this.watchTimer = setInterval(checkSize, 1000); +}; + +/** + * Stop watching for a resize of the frame. + * @private + */ +RootPanel.prototype._unwatch = function () { + if (this.watchTimer) { + clearInterval(this.watchTimer); + this.watchTimer = undefined; + } + + // TODO: remove event listener on window.resize +}; + +/** + * Set controller for this component, or remove current controller by passing + * null as parameter value. + * @param {Controller | null} controller + */ +RootPanel.prototype.setController = function setController (controller) { + this.controller = controller || null; + + if (this.controller) { + this._registerListeners(); + } + else { + this._unregisterListeners(); + } +}; + +/** + * Register event emitters emitted by the rootpanel + * @private + */ +RootPanel.prototype._registerListeners = function () { + if (this.frame && this.controller && !this.hammer) { + this.hammer = Hammer(this.frame, { + prevent_default: true + }); + + for (var event in this.listeners) { + if (this.listeners.hasOwnProperty(event)) { + this.hammer.on(event, this.listeners[event]); + } + } + } +}; + +/** + * Unregister event emitters from the rootpanel + * @private + */ +RootPanel.prototype._unregisterListeners = function () { + if (this.hammer) { + for (var event in this.listeners) { + if (this.listeners.hasOwnProperty(event)) { + this.hammer.off(event, this.listeners[event]); + } + } + + this.hammer = null; + } +}; + +/** + * A horizontal time axis + * @param {Component} parent + * @param {Component[]} [depends] Components on which this components depends + * (except for the parent) + * @param {Object} [options] See TimeAxis.setOptions for the available + * options. + * @constructor TimeAxis + * @extends Component + */ +function TimeAxis (parent, depends, options) { + this.id = util.randomUUID(); + this.parent = parent; + this.depends = depends; + + this.dom = { + majorLines: [], + majorTexts: [], + minorLines: [], + minorTexts: [], + redundant: { + majorLines: [], + majorTexts: [], + minorLines: [], + minorTexts: [] + } + }; + this.props = { + range: { + start: 0, + end: 0, + minimumStep: 0 + }, + lineTop: 0 + }; + + this.options = options || {}; + this.defaultOptions = { + orientation: 'bottom', // supported: 'top', 'bottom' + // TODO: implement timeaxis orientations 'left' and 'right' + showMinorLabels: true, + showMajorLabels: true + }; + + this.conversion = null; + this.range = null; +} + +TimeAxis.prototype = new Component(); + +// TODO: comment options +TimeAxis.prototype.setOptions = Component.prototype.setOptions; + +/** + * Set a range (start and end) + * @param {Range | Object} range A Range or an object containing start and end. + */ +TimeAxis.prototype.setRange = function (range) { + if (!(range instanceof Range) && (!range || !range.start || !range.end)) { + throw new TypeError('Range must be an instance of Range, ' + + 'or an object containing start and end.'); + } + this.range = range; +}; + +/** + * Convert a position on screen (pixels) to a datetime + * @param {int} x Position on the screen in pixels + * @return {Date} time The datetime the corresponds with given position x + */ +TimeAxis.prototype.toTime = function(x) { + var conversion = this.conversion; + return new Date(x / conversion.scale + conversion.offset); +}; + +/** + * Convert a datetime (Date object) into a position on the screen + * @param {Date} time A date + * @return {int} x The position on the screen in pixels which corresponds + * with the given date. + * @private + */ +TimeAxis.prototype.toScreen = function(time) { + var conversion = this.conversion; + return (time.valueOf() - conversion.offset) * conversion.scale; +}; + +/** + * Repaint the component + * @return {Boolean} changed + */ +TimeAxis.prototype.repaint = function () { + var changed = 0, + update = util.updateProperty, + asSize = util.option.asSize, + options = this.options, + orientation = this.getOption('orientation'), + props = this.props, + step = this.step; + + var frame = this.frame; + if (!frame) { + frame = document.createElement('div'); + this.frame = frame; + changed += 1; + } + frame.className = 'axis'; + // TODO: custom className? + + if (!frame.parentNode) { + if (!this.parent) { + throw new Error('Cannot repaint time axis: no parent attached'); + } + var parentContainer = this.parent.getContainer(); + if (!parentContainer) { + throw new Error('Cannot repaint time axis: parent has no container element'); + } + parentContainer.appendChild(frame); + + changed += 1; + } + + var parent = frame.parentNode; + if (parent) { + var beforeChild = frame.nextSibling; + parent.removeChild(frame); // take frame offline while updating (is almost twice as fast) + + var defaultTop = (orientation == 'bottom' && this.props.parentHeight && this.height) ? + (this.props.parentHeight - this.height) + 'px' : + '0px'; + changed += update(frame.style, 'top', asSize(options.top, defaultTop)); + changed += update(frame.style, 'left', asSize(options.left, '0px')); + changed += update(frame.style, 'width', asSize(options.width, '100%')); + changed += update(frame.style, 'height', asSize(options.height, this.height + 'px')); + + // get characters width and height + this._repaintMeasureChars(); + + if (this.step) { + this._repaintStart(); + + step.first(); + var xFirstMajorLabel = undefined; + var max = 0; + while (step.hasNext() && max < 1000) { + max++; + var cur = step.getCurrent(), + x = this.toScreen(cur), + isMajor = step.isMajor(); + + // TODO: lines must have a width, such that we can create css backgrounds + + if (this.getOption('showMinorLabels')) { + this._repaintMinorText(x, step.getLabelMinor()); + } + + if (isMajor && this.getOption('showMajorLabels')) { + if (x > 0) { + if (xFirstMajorLabel == undefined) { + xFirstMajorLabel = x; + } + this._repaintMajorText(x, step.getLabelMajor()); + } + this._repaintMajorLine(x); + } + else { + this._repaintMinorLine(x); + } + + step.next(); + } + + // create a major label on the left when needed + if (this.getOption('showMajorLabels')) { + var leftTime = this.toTime(0), + leftText = step.getLabelMajor(leftTime), + widthText = leftText.length * (props.majorCharWidth || 10) + 10; // upper bound estimation + + if (xFirstMajorLabel == undefined || widthText < xFirstMajorLabel) { + this._repaintMajorText(0, leftText); + } + } + + this._repaintEnd(); + } + + this._repaintLine(); + + // put frame online again + if (beforeChild) { + parent.insertBefore(frame, beforeChild); + } + else { + parent.appendChild(frame) + } + } + + return (changed > 0); +}; + +/** + * Start a repaint. Move all DOM elements to a redundant list, where they + * can be picked for re-use, or can be cleaned up in the end + * @private + */ +TimeAxis.prototype._repaintStart = function () { + var dom = this.dom, + redundant = dom.redundant; + + redundant.majorLines = dom.majorLines; + redundant.majorTexts = dom.majorTexts; + redundant.minorLines = dom.minorLines; + redundant.minorTexts = dom.minorTexts; + + dom.majorLines = []; + dom.majorTexts = []; + dom.minorLines = []; + dom.minorTexts = []; +}; + +/** + * End a repaint. Cleanup leftover DOM elements in the redundant list + * @private + */ +TimeAxis.prototype._repaintEnd = function () { + util.forEach(this.dom.redundant, function (arr) { + while (arr.length) { + var elem = arr.pop(); + if (elem && elem.parentNode) { + elem.parentNode.removeChild(elem); + } + } + }); +}; + + +/** + * Create a minor label for the axis at position x + * @param {Number} x + * @param {String} text + * @private + */ +TimeAxis.prototype._repaintMinorText = function (x, text) { + // reuse redundant label + var label = this.dom.redundant.minorTexts.shift(); + + if (!label) { + // create new label + var content = document.createTextNode(''); + label = document.createElement('div'); + label.appendChild(content); + label.className = 'text minor'; + this.frame.appendChild(label); + } + this.dom.minorTexts.push(label); + + label.childNodes[0].nodeValue = text; + label.style.left = x + 'px'; + label.style.top = this.props.minorLabelTop + 'px'; + //label.title = title; // TODO: this is a heavy operation +}; + +/** + * Create a Major label for the axis at position x + * @param {Number} x + * @param {String} text + * @private + */ +TimeAxis.prototype._repaintMajorText = function (x, text) { + // reuse redundant label + var label = this.dom.redundant.majorTexts.shift(); + + if (!label) { + // create label + var content = document.createTextNode(text); + label = document.createElement('div'); + label.className = 'text major'; + label.appendChild(content); + this.frame.appendChild(label); + } + this.dom.majorTexts.push(label); + + label.childNodes[0].nodeValue = text; + label.style.top = this.props.majorLabelTop + 'px'; + label.style.left = x + 'px'; + //label.title = title; // TODO: this is a heavy operation +}; + +/** + * Create a minor line for the axis at position x + * @param {Number} x + * @private + */ +TimeAxis.prototype._repaintMinorLine = function (x) { + // reuse redundant line + var line = this.dom.redundant.minorLines.shift(); + + if (!line) { + // create vertical line + line = document.createElement('div'); + line.className = 'grid vertical minor'; + this.frame.appendChild(line); + } + this.dom.minorLines.push(line); + + var props = this.props; + line.style.top = props.minorLineTop + 'px'; + line.style.height = props.minorLineHeight + 'px'; + line.style.left = (x - props.minorLineWidth / 2) + 'px'; +}; + +/** + * Create a Major line for the axis at position x + * @param {Number} x + * @private + */ +TimeAxis.prototype._repaintMajorLine = function (x) { + // reuse redundant line + var line = this.dom.redundant.majorLines.shift(); + + if (!line) { + // create vertical line + line = document.createElement('DIV'); + line.className = 'grid vertical major'; + this.frame.appendChild(line); + } + this.dom.majorLines.push(line); + + var props = this.props; + line.style.top = props.majorLineTop + 'px'; + line.style.left = (x - props.majorLineWidth / 2) + 'px'; + line.style.height = props.majorLineHeight + 'px'; +}; + + +/** + * Repaint the horizontal line for the axis + * @private + */ +TimeAxis.prototype._repaintLine = function() { + var line = this.dom.line, + frame = this.frame, + options = this.options; + + // line before all axis elements + if (this.getOption('showMinorLabels') || this.getOption('showMajorLabels')) { + if (line) { + // put this line at the end of all childs + frame.removeChild(line); + frame.appendChild(line); + } + else { + // create the axis line + line = document.createElement('div'); + line.className = 'grid horizontal major'; + frame.appendChild(line); + this.dom.line = line; + } + + line.style.top = this.props.lineTop + 'px'; + } + else { + if (line && line.parentElement) { + frame.removeChild(line.line); + delete this.dom.line; + } + } +}; + +/** + * Create characters used to determine the size of text on the axis + * @private + */ +TimeAxis.prototype._repaintMeasureChars = function () { + // calculate the width and height of a single character + // this is used to calculate the step size, and also the positioning of the + // axis + var dom = this.dom, + text; + + if (!dom.measureCharMinor) { + text = document.createTextNode('0'); + var measureCharMinor = document.createElement('DIV'); + measureCharMinor.className = 'text minor measure'; + measureCharMinor.appendChild(text); + this.frame.appendChild(measureCharMinor); + + dom.measureCharMinor = measureCharMinor; + } + + if (!dom.measureCharMajor) { + text = document.createTextNode('0'); + var measureCharMajor = document.createElement('DIV'); + measureCharMajor.className = 'text major measure'; + measureCharMajor.appendChild(text); + this.frame.appendChild(measureCharMajor); + + dom.measureCharMajor = measureCharMajor; + } +}; + +/** + * Reflow the component + * @return {Boolean} resized + */ +TimeAxis.prototype.reflow = function () { + var changed = 0, + update = util.updateProperty, + frame = this.frame, + range = this.range; + + if (!range) { + throw new Error('Cannot repaint time axis: no range configured'); + } + + if (frame) { + changed += update(this, 'top', frame.offsetTop); + changed += update(this, 'left', frame.offsetLeft); + + // calculate size of a character + var props = this.props, + showMinorLabels = this.getOption('showMinorLabels'), + showMajorLabels = this.getOption('showMajorLabels'), + measureCharMinor = this.dom.measureCharMinor, + measureCharMajor = this.dom.measureCharMajor; + if (measureCharMinor) { + props.minorCharHeight = measureCharMinor.clientHeight; + props.minorCharWidth = measureCharMinor.clientWidth; + } + if (measureCharMajor) { + props.majorCharHeight = measureCharMajor.clientHeight; + props.majorCharWidth = measureCharMajor.clientWidth; + } + + var parentHeight = frame.parentNode ? frame.parentNode.offsetHeight : 0; + if (parentHeight != props.parentHeight) { + props.parentHeight = parentHeight; + changed += 1; + } + switch (this.getOption('orientation')) { + case 'bottom': + props.minorLabelHeight = showMinorLabels ? props.minorCharHeight : 0; + props.majorLabelHeight = showMajorLabels ? props.majorCharHeight : 0; + + props.minorLabelTop = 0; + props.majorLabelTop = props.minorLabelTop + props.minorLabelHeight; + + props.minorLineTop = -this.top; + props.minorLineHeight = Math.max(this.top + props.majorLabelHeight, 0); + props.minorLineWidth = 1; // TODO: really calculate width + + props.majorLineTop = -this.top; + props.majorLineHeight = Math.max(this.top + props.minorLabelHeight + props.majorLabelHeight, 0); + props.majorLineWidth = 1; // TODO: really calculate width + + props.lineTop = 0; + + break; + + case 'top': + props.minorLabelHeight = showMinorLabels ? props.minorCharHeight : 0; + props.majorLabelHeight = showMajorLabels ? props.majorCharHeight : 0; + + props.majorLabelTop = 0; + props.minorLabelTop = props.majorLabelTop + props.majorLabelHeight; + + props.minorLineTop = props.minorLabelTop; + props.minorLineHeight = Math.max(parentHeight - props.majorLabelHeight - this.top); + props.minorLineWidth = 1; // TODO: really calculate width + + props.majorLineTop = 0; + props.majorLineHeight = Math.max(parentHeight - this.top); + props.majorLineWidth = 1; // TODO: really calculate width + + props.lineTop = props.majorLabelHeight + props.minorLabelHeight; + + break; + + default: + throw new Error('Unkown orientation "' + this.getOption('orientation') + '"'); + } + + var height = props.minorLabelHeight + props.majorLabelHeight; + changed += update(this, 'width', frame.offsetWidth); + changed += update(this, 'height', height); + + // calculate range and step + this._updateConversion(); + + var start = util.convert(range.start, 'Number'), + end = util.convert(range.end, 'Number'), + minimumStep = this.toTime((props.minorCharWidth || 10) * 5).valueOf() + -this.toTime(0).valueOf(); + this.step = new TimeStep(new Date(start), new Date(end), minimumStep); + changed += update(props.range, 'start', start); + changed += update(props.range, 'end', end); + changed += update(props.range, 'minimumStep', minimumStep.valueOf()); + } + + return (changed > 0); +}; + +/** + * Calculate the scale and offset to convert a position on screen to the + * corresponding date and vice versa. + * After the method _updateConversion is executed once, the methods toTime + * and toScreen can be used. + * @private + */ +TimeAxis.prototype._updateConversion = function() { + var range = this.range; + if (!range) { + throw new Error('No range configured'); + } + + if (range.conversion) { + this.conversion = range.conversion(this.width); + } + else { + this.conversion = Range.conversion(range.start, range.end, this.width); + } +}; + +/** + * Snap a date to a rounded value. + * The snap intervals are dependent on the current scale and step. + * @param {Date} date the date to be snapped. + * @return {Date} snappedDate + */ +TimeAxis.prototype.snap = function snap (date) { + return this.step.snap(date); +}; + +/** + * A current time bar + * @param {Component} parent + * @param {Component[]} [depends] Components on which this components depends + * (except for the parent) + * @param {Object} [options] Available parameters: + * {Boolean} [showCurrentTime] + * @constructor CurrentTime + * @extends Component + */ + +function CurrentTime (parent, depends, options) { + this.id = util.randomUUID(); + this.parent = parent; + this.depends = depends; + + this.options = options || {}; + this.defaultOptions = { + showCurrentTime: false + }; +} + +CurrentTime.prototype = new Component(); + +CurrentTime.prototype.setOptions = Component.prototype.setOptions; + +/** + * Get the container element of the bar, which can be used by a child to + * add its own widgets. + * @returns {HTMLElement} container + */ +CurrentTime.prototype.getContainer = function () { + return this.frame; +}; + +/** + * Repaint the component + * @return {Boolean} changed + */ +CurrentTime.prototype.repaint = function () { + var bar = this.frame, + parent = this.parent, + parentContainer = parent.parent.getContainer(); + + if (!parent) { + throw new Error('Cannot repaint bar: no parent attached'); + } + + if (!parentContainer) { + throw new Error('Cannot repaint bar: parent has no container element'); + } + + if (!this.getOption('showCurrentTime')) { + if (bar) { + parentContainer.removeChild(bar); + delete this.frame; + } + + return false; + } + + if (!bar) { + bar = document.createElement('div'); + bar.className = 'currenttime'; + bar.style.position = 'absolute'; + bar.style.top = '0px'; + bar.style.height = '100%'; + + parentContainer.appendChild(bar); + this.frame = bar; + } + + if (!parent.conversion) { + parent._updateConversion(); + } + + var now = new Date(); + var x = parent.toScreen(now); + + bar.style.left = x + 'px'; + bar.title = 'Current time: ' + now; + + // start a timer to adjust for the new time + if (this.currentTimeTimer !== undefined) { + clearTimeout(this.currentTimeTimer); + delete this.currentTimeTimer; + } + + var timeline = this; + var interval = 1 / parent.conversion.scale / 2; + + if (interval < 30) { + interval = 30; + } + + this.currentTimeTimer = setTimeout(function() { + timeline.repaint(); + }, interval); + + return false; +}; + +/** + * A custom time bar + * @param {Component} parent + * @param {Component[]} [depends] Components on which this components depends + * (except for the parent) + * @param {Object} [options] Available parameters: + * {Boolean} [showCustomTime] + * @constructor CustomTime + * @extends Component + */ + +function CustomTime (parent, depends, options) { + this.id = util.randomUUID(); + this.parent = parent; + this.depends = depends; + + this.options = options || {}; + this.defaultOptions = { + showCustomTime: false + }; + + this.customTime = new Date(); + this.eventParams = {}; // stores state parameters while dragging the bar +} + +CustomTime.prototype = new Component(); + +Emitter(CustomTime.prototype); + +CustomTime.prototype.setOptions = Component.prototype.setOptions; + +/** + * Get the container element of the bar, which can be used by a child to + * add its own widgets. + * @returns {HTMLElement} container + */ +CustomTime.prototype.getContainer = function () { + return this.frame; +}; + +/** + * Repaint the component + * @return {Boolean} changed + */ +CustomTime.prototype.repaint = function () { + var bar = this.frame, + parent = this.parent; + + if (!parent) { + throw new Error('Cannot repaint bar: no parent attached'); + } + + var parentContainer = parent.parent.getContainer(); + if (!parentContainer) { + throw new Error('Cannot repaint bar: parent has no container element'); + } + + if (!this.getOption('showCustomTime')) { + if (bar) { + parentContainer.removeChild(bar); + delete this.frame; + } + + return false; + } + + if (!bar) { + bar = document.createElement('div'); + bar.className = 'customtime'; + bar.style.position = 'absolute'; + bar.style.top = '0px'; + bar.style.height = '100%'; + + parentContainer.appendChild(bar); + + var drag = document.createElement('div'); + drag.style.position = 'relative'; + drag.style.top = '0px'; + drag.style.left = '-10px'; + drag.style.height = '100%'; + drag.style.width = '20px'; + bar.appendChild(drag); + + this.frame = bar; + + // attach event listeners + this.hammer = Hammer(bar, { + prevent_default: true + }); + this.hammer.on('dragstart', this._onDragStart.bind(this)); + this.hammer.on('drag', this._onDrag.bind(this)); + this.hammer.on('dragend', this._onDragEnd.bind(this)); + } + + if (!parent.conversion) { + parent._updateConversion(); + } + + var x = parent.toScreen(this.customTime); + + bar.style.left = x + 'px'; + bar.title = 'Time: ' + this.customTime; + + return false; +}; + +/** + * Set custom time. + * @param {Date} time + */ +CustomTime.prototype.setCustomTime = function(time) { + this.customTime = new Date(time.valueOf()); + this.repaint(); +}; + +/** + * Retrieve the current custom time. + * @return {Date} customTime + */ +CustomTime.prototype.getCustomTime = function() { + return new Date(this.customTime.valueOf()); +}; + +/** + * Start moving horizontally + * @param {Event} event + * @private + */ +CustomTime.prototype._onDragStart = function(event) { + this.eventParams.customTime = this.customTime; + + event.stopPropagation(); + event.preventDefault(); +}; + +/** + * Perform moving operating. + * @param {Event} event + * @private + */ +CustomTime.prototype._onDrag = function (event) { + var deltaX = event.gesture.deltaX, + x = this.parent.toScreen(this.eventParams.customTime) + deltaX, + time = this.parent.toTime(x); + + this.setCustomTime(time); + + // fire a timechange event + if (this.controller) { + this.controller.emit('timechange', { + time: this.customTime + }) + } + + event.stopPropagation(); + event.preventDefault(); +}; + +/** + * Stop moving operating. + * @param {event} event + * @private + */ +CustomTime.prototype._onDragEnd = function (event) { + // fire a timechanged event + if (this.controller) { + this.controller.emit('timechanged', { + time: this.customTime + }) + } + + event.stopPropagation(); + event.preventDefault(); +}; + +/** + * An ItemSet holds a set of items and ranges which can be displayed in a + * range. The width is determined by the parent of the ItemSet, and the height + * is determined by the size of the items. + * @param {Component} parent + * @param {Component[]} [depends] Components on which this components depends + * (except for the parent) + * @param {Object} [options] See ItemSet.setOptions for the available + * options. + * @constructor ItemSet + * @extends Panel + */ +// TODO: improve performance by replacing all Array.forEach with a for loop +function ItemSet(parent, depends, options) { + this.id = util.randomUUID(); + this.parent = parent; + this.depends = depends; + + // event listeners + this.eventListeners = { + dragstart: this._onDragStart.bind(this), + drag: this._onDrag.bind(this), + dragend: this._onDragEnd.bind(this) + }; + + // one options object is shared by this itemset and all its items + this.options = options || {}; + this.defaultOptions = { + type: 'box', + align: 'center', + orientation: 'bottom', + margin: { + axis: 20, + item: 10 + }, + padding: 5 + }; + + this.dom = {}; + + var me = this; + this.itemsData = null; // DataSet + this.range = null; // Range or Object {start: number, end: number} + + // data change listeners + this.listeners = { + 'add': function (event, params, senderId) { + if (senderId != me.id) { + me._onAdd(params.items); + } + }, + 'update': function (event, params, senderId) { + if (senderId != me.id) { + me._onUpdate(params.items); + } + }, + 'remove': function (event, params, senderId) { + if (senderId != me.id) { + me._onRemove(params.items); + } + } + }; + + this.items = {}; // object with an Item for every data item + this.selection = []; // list with the ids of all selected nodes + this.queue = {}; // queue with id/actions: 'add', 'update', 'delete' + this.stack = new Stack(this, Object.create(this.options)); + this.conversion = null; + + this.touchParams = {}; // stores properties while dragging + + // TODO: ItemSet should also attach event listeners for rangechange and rangechanged, like timeaxis +} + +ItemSet.prototype = new Panel(); + +// available item types will be registered here +ItemSet.types = { + box: ItemBox, + range: ItemRange, + rangeoverflow: ItemRangeOverflow, + point: ItemPoint +}; + +/** + * Set options for the ItemSet. Existing options will be extended/overwritten. + * @param {Object} [options] The following options are available: + * {String | function} [className] + * class name for the itemset + * {String} [type] + * Default type for the items. Choose from 'box' + * (default), 'point', or 'range'. The default + * Style can be overwritten by individual items. + * {String} align + * Alignment for the items, only applicable for + * ItemBox. Choose 'center' (default), 'left', or + * 'right'. + * {String} orientation + * Orientation of the item set. Choose 'top' or + * 'bottom' (default). + * {Number} margin.axis + * Margin between the axis and the items in pixels. + * Default is 20. + * {Number} margin.item + * Margin between items in pixels. Default is 10. + * {Number} padding + * Padding of the contents of an item in pixels. + * Must correspond with the items css. Default is 5. + * {Function} snap + * Function to let items snap to nice dates when + * dragging items. + */ +ItemSet.prototype.setOptions = Component.prototype.setOptions; + + + +/** + * Set controller for this component + * @param {Controller | null} controller + */ +ItemSet.prototype.setController = function setController (controller) { + var event; + + // unregister old event listeners + if (this.controller) { + for (event in this.eventListeners) { + if (this.eventListeners.hasOwnProperty(event)) { + this.controller.off(event, this.eventListeners[event]); + } + } + } + + this.controller = controller || null; + + // register new event listeners + if (this.controller) { + for (event in this.eventListeners) { + if (this.eventListeners.hasOwnProperty(event)) { + this.controller.on(event, this.eventListeners[event]); + } + } + } +}; + +// attach event listeners for dragging items to the controller +(function (me) { + var _controller = null; + var _onDragStart = null; + var _onDrag = null; + var _onDragEnd = null; + + Object.defineProperty(me, 'controller', { + get: function () { + return _controller; + }, + + set: function (controller) { + + } + }); +}) (this); + + +/** + * Set range (start and end). + * @param {Range | Object} range A Range or an object containing start and end. + */ +ItemSet.prototype.setRange = function setRange(range) { + if (!(range instanceof Range) && (!range || !range.start || !range.end)) { + throw new TypeError('Range must be an instance of Range, ' + + 'or an object containing start and end.'); + } + this.range = range; +}; + +/** + * Set selected items by their id. Replaces the current selection + * Unknown id's are silently ignored. + * @param {Array} [ids] An array with zero or more id's of the items to be + * selected. If ids is an empty array, all items will be + * unselected. + */ +ItemSet.prototype.setSelection = function setSelection(ids) { + var i, ii, id, item, selection; + + if (ids) { + if (!Array.isArray(ids)) { + throw new TypeError('Array expected'); + } + + // unselect currently selected items + for (i = 0, ii = this.selection.length; i < ii; i++) { + id = this.selection[i]; + item = this.items[id]; + if (item) item.unselect(); + } + + // select items + this.selection = []; + for (i = 0, ii = ids.length; i < ii; i++) { + id = ids[i]; + item = this.items[id]; + if (item) { + this.selection.push(id); + item.select(); + } + } + + if (this.controller) { + this.requestRepaint(); + } + } +}; + +/** + * Get the selected items by their id + * @return {Array} ids The ids of the selected items + */ +ItemSet.prototype.getSelection = function getSelection() { + return this.selection.concat([]); +}; + +/** + * Deselect a selected item + * @param {String | Number} id + * @private + */ +ItemSet.prototype._deselect = function _deselect(id) { + var selection = this.selection; + for (var i = 0, ii = selection.length; i < ii; i++) { + if (selection[i] == id) { // non-strict comparison! + selection.splice(i, 1); + break; + } + } +}; + +/** + * Repaint the component + * @return {Boolean} changed + */ +ItemSet.prototype.repaint = function repaint() { + var changed = 0, + update = util.updateProperty, + asSize = util.option.asSize, + options = this.options, + orientation = this.getOption('orientation'), + defaultOptions = this.defaultOptions, + frame = this.frame; + + if (!frame) { + frame = document.createElement('div'); + frame.className = 'itemset'; + frame['timeline-itemset'] = this; + + var className = options.className; + if (className) { + util.addClassName(frame, util.option.asString(className)); + } + + // create background panel + var background = document.createElement('div'); + background.className = 'background'; + frame.appendChild(background); + this.dom.background = background; + + // create foreground panel + var foreground = document.createElement('div'); + foreground.className = 'foreground'; + frame.appendChild(foreground); + this.dom.foreground = foreground; + + // create axis panel + var axis = document.createElement('div'); + axis.className = 'itemset-axis'; + //frame.appendChild(axis); + this.dom.axis = axis; + + this.frame = frame; + changed += 1; + } + + if (!this.parent) { + throw new Error('Cannot repaint itemset: no parent attached'); + } + var parentContainer = this.parent.getContainer(); + if (!parentContainer) { + throw new Error('Cannot repaint itemset: parent has no container element'); + } + if (!frame.parentNode) { + parentContainer.appendChild(frame); + changed += 1; + } + if (!this.dom.axis.parentNode) { + parentContainer.appendChild(this.dom.axis); + changed += 1; + } + + // reposition frame + changed += update(frame.style, 'left', asSize(options.left, '0px')); + changed += update(frame.style, 'top', asSize(options.top, '0px')); + changed += update(frame.style, 'width', asSize(options.width, '100%')); + changed += update(frame.style, 'height', asSize(options.height, this.height + 'px')); + + // reposition axis + changed += update(this.dom.axis.style, 'left', asSize(options.left, '0px')); + changed += update(this.dom.axis.style, 'width', asSize(options.width, '100%')); + if (orientation == 'bottom') { + changed += update(this.dom.axis.style, 'top', (this.height + this.top) + 'px'); + } + else { // orientation == 'top' + changed += update(this.dom.axis.style, 'top', this.top + 'px'); + } + + this._updateConversion(); + + var me = this, + queue = this.queue, + itemsData = this.itemsData, + items = this.items, + dataOptions = { + // TODO: cleanup + // fields: [(itemsData && itemsData.fieldId || 'id'), 'start', 'end', 'content', 'type', 'className'] + }; + + // show/hide added/changed/removed items + for (var id in queue) { + if (queue.hasOwnProperty(id)) { + var entry = queue[id], + item = items[id], + action = entry.action; + + //noinspection FallthroughInSwitchStatementJS + switch (action) { + case 'add': + case 'update': + var itemData = itemsData && itemsData.get(id, dataOptions); + + if (itemData) { + var type = itemData.type || + (itemData.start && itemData.end && 'range') || + options.type || + 'box'; + var constructor = ItemSet.types[type]; + + // TODO: how to handle items with invalid data? hide them and give a warning? or throw an error? + if (item) { + // update item + if (!constructor || !(item instanceof constructor)) { + // item type has changed, hide and delete the item + changed += item.hide(); + item = null; + } + else { + item.data = itemData; // TODO: create a method item.setData ? + changed++; + } + } + + if (!item) { + // create item + if (constructor) { + item = new constructor(me, itemData, options, defaultOptions); + item.id = entry.id; // we take entry.id, as id itself is stringified + changed++; + } + else { + throw new TypeError('Unknown item type "' + type + '"'); + } + } + + // force a repaint (not only a reposition) + item.repaint(); + + items[id] = item; + } + + // update queue + delete queue[id]; + break; + + case 'remove': + if (item) { + // remove the item from the set selected items + if (item.selected) { + me._deselect(id); + } + + // remove DOM of the item + changed += item.hide(); + } + + // update lists + delete items[id]; + delete queue[id]; + break; + + default: + console.log('Error: unknown action "' + action + '"'); + } + } + } + + // reposition all items. Show items only when in the visible area + util.forEach(this.items, function (item) { + if (item.visible) { + changed += item.show(); + item.reposition(); + } + else { + changed += item.hide(); + } + }); + + return (changed > 0); +}; + +/** + * Get the foreground container element + * @return {HTMLElement} foreground + */ +ItemSet.prototype.getForeground = function getForeground() { + return this.dom.foreground; +}; + +/** + * Get the background container element + * @return {HTMLElement} background + */ +ItemSet.prototype.getBackground = function getBackground() { + return this.dom.background; +}; + +/** + * Get the axis container element + * @return {HTMLElement} axis + */ +ItemSet.prototype.getAxis = function getAxis() { + return this.dom.axis; +}; + +/** + * Reflow the component + * @return {Boolean} resized + */ +ItemSet.prototype.reflow = function reflow () { + var changed = 0, + options = this.options, + marginAxis = (options.margin && 'axis' in options.margin) ? options.margin.axis : this.defaultOptions.margin.axis, + marginItem = (options.margin && 'item' in options.margin) ? options.margin.item : this.defaultOptions.margin.item, + update = util.updateProperty, + asNumber = util.option.asNumber, + asSize = util.option.asSize, + frame = this.frame; + + if (frame) { + this._updateConversion(); + + util.forEach(this.items, function (item) { + changed += item.reflow(); + }); + + // TODO: stack.update should be triggered via an event, in stack itself + // TODO: only update the stack when there are changed items + this.stack.update(); + + var maxHeight = asNumber(options.maxHeight); + var fixedHeight = (asSize(options.height) != null); + var height; + if (fixedHeight) { + height = frame.offsetHeight; + } + else { + // height is not specified, determine the height from the height and positioned items + var visibleItems = this.stack.ordered; // TODO: not so nice way to get the filtered items + if (visibleItems.length) { + var min = visibleItems[0].top; + var max = visibleItems[0].top + visibleItems[0].height; + util.forEach(visibleItems, function (item) { + min = Math.min(min, item.top); + max = Math.max(max, (item.top + item.height)); + }); + height = (max - min) + marginAxis + marginItem; + } + else { + height = marginAxis + marginItem; + } + } + if (maxHeight != null) { + height = Math.min(height, maxHeight); + } + changed += update(this, 'height', height); + + // calculate height from items + changed += update(this, 'top', frame.offsetTop); + changed += update(this, 'left', frame.offsetLeft); + changed += update(this, 'width', frame.offsetWidth); + } + else { + changed += 1; + } + + return (changed > 0); +}; + +/** + * Hide this component from the DOM + * @return {Boolean} changed + */ +ItemSet.prototype.hide = function hide() { + var changed = false; + + // remove the DOM + if (this.frame && this.frame.parentNode) { + this.frame.parentNode.removeChild(this.frame); + changed = true; + } + if (this.dom.axis && this.dom.axis.parentNode) { + this.dom.axis.parentNode.removeChild(this.dom.axis); + changed = true; + } + + return changed; +}; + +/** + * Set items + * @param {vis.DataSet | null} items + */ +ItemSet.prototype.setItems = function setItems(items) { + var me = this, + ids, + oldItemsData = this.itemsData; + + // replace the dataset + if (!items) { + this.itemsData = null; + } + else if (items instanceof DataSet || items instanceof DataView) { + this.itemsData = items; + } + else { + throw new TypeError('Data must be an instance of DataSet'); + } + + if (oldItemsData) { + // unsubscribe from old dataset + util.forEach(this.listeners, function (callback, event) { + oldItemsData.unsubscribe(event, callback); + }); + + // remove all drawn items + ids = oldItemsData.getIds(); + this._onRemove(ids); + } + + if (this.itemsData) { + // subscribe to new dataset + var id = this.id; + util.forEach(this.listeners, function (callback, event) { + me.itemsData.on(event, callback, id); + }); + + // draw all new items + ids = this.itemsData.getIds(); + this._onAdd(ids); + } +}; + +/** + * Get the current items items + * @returns {vis.DataSet | null} + */ +ItemSet.prototype.getItems = function getItems() { + return this.itemsData; +}; + +/** + * Remove an item by its id + * @param {String | Number} id + */ +ItemSet.prototype.removeItem = function removeItem (id) { + var item = this.itemsData.get(id), + dataset = this._myDataSet(); + + if (item) { + // confirm deletion + this.options.onRemove(item, function (item) { + if (item) { + dataset.remove(item); + } + }); + } +}; + +/** + * Handle updated items + * @param {Number[]} ids + * @private + */ +ItemSet.prototype._onUpdate = function _onUpdate(ids) { + this._toQueue('update', ids); +}; + +/** + * Handle changed items + * @param {Number[]} ids + * @private + */ +ItemSet.prototype._onAdd = function _onAdd(ids) { + this._toQueue('add', ids); +}; + +/** + * Handle removed items + * @param {Number[]} ids + * @private + */ +ItemSet.prototype._onRemove = function _onRemove(ids) { + this._toQueue('remove', ids); +}; + +/** + * Put items in the queue to be added/updated/remove + * @param {String} action can be 'add', 'update', 'remove' + * @param {Number[]} ids + */ +ItemSet.prototype._toQueue = function _toQueue(action, ids) { + var queue = this.queue; + ids.forEach(function (id) { + queue[id] = { + id: id, + action: action + }; + }); + + if (this.controller) { + //this.requestReflow(); + this.requestRepaint(); + } +}; + +/** + * Calculate the scale and offset to convert a position on screen to the + * corresponding date and vice versa. + * After the method _updateConversion is executed once, the methods toTime + * and toScreen can be used. + * @private + */ +ItemSet.prototype._updateConversion = function _updateConversion() { + var range = this.range; + if (!range) { + throw new Error('No range configured'); + } + + if (range.conversion) { + this.conversion = range.conversion(this.width); + } + else { + this.conversion = Range.conversion(range.start, range.end, this.width); + } +}; + +/** + * Convert a position on screen (pixels) to a datetime + * Before this method can be used, the method _updateConversion must be + * executed once. + * @param {int} x Position on the screen in pixels + * @return {Date} time The datetime the corresponds with given position x + */ +ItemSet.prototype.toTime = function toTime(x) { + var conversion = this.conversion; + return new Date(x / conversion.scale + conversion.offset); +}; + +/** + * Convert a datetime (Date object) into a position on the screen + * Before this method can be used, the method _updateConversion must be + * executed once. + * @param {Date} time A date + * @return {int} x The position on the screen in pixels which corresponds + * with the given date. + */ +ItemSet.prototype.toScreen = function toScreen(time) { + var conversion = this.conversion; + return (time.valueOf() - conversion.offset) * conversion.scale; +}; + +/** + * Start dragging the selected events + * @param {Event} event + * @private + */ +ItemSet.prototype._onDragStart = function (event) { + if (!this.options.editable) { + return; + } + + var item = ItemSet.itemFromTarget(event), + me = this; + + if (item && item.selected) { + var dragLeftItem = event.target.dragLeftItem; + var dragRightItem = event.target.dragRightItem; + + if (dragLeftItem) { + this.touchParams.itemProps = [{ + item: dragLeftItem, + start: item.data.start.valueOf() + }]; + } + else if (dragRightItem) { + this.touchParams.itemProps = [{ + item: dragRightItem, + end: item.data.end.valueOf() + }]; + } + else { + this.touchParams.itemProps = this.getSelection().map(function (id) { + var item = me.items[id]; + var props = { + item: item + }; + + if ('start' in item.data) { + props.start = item.data.start.valueOf() + } + if ('end' in item.data) { + props.end = item.data.end.valueOf() + } + + return props; + }); + } + + event.stopPropagation(); + } +}; + +/** + * Drag selected items + * @param {Event} event + * @private + */ +ItemSet.prototype._onDrag = function (event) { + if (this.touchParams.itemProps) { + var snap = this.options.snap || null, + deltaX = event.gesture.deltaX, + offset = deltaX / this.conversion.scale; + + // move + this.touchParams.itemProps.forEach(function (props) { + if ('start' in props) { + var start = new Date(props.start + offset); + props.item.data.start = snap ? snap(start) : start; + } + if ('end' in props) { + var end = new Date(props.end + offset); + props.item.data.end = snap ? snap(end) : end; + } + }); + + // TODO: implement onMoving handler + + // TODO: implement dragging from one group to another + + this.requestReflow(); + + event.stopPropagation(); + } +}; + +/** + * End of dragging selected items + * @param {Event} event + * @private + */ +ItemSet.prototype._onDragEnd = function (event) { + if (this.touchParams.itemProps) { + // prepare a change set for the changed items + var changes = [], + me = this, + dataset = this._myDataSet(), + type; + + this.touchParams.itemProps.forEach(function (props) { + var id = props.item.id, + item = me.itemsData.get(id); + + var changed = false; + if ('start' in props.item.data) { + changed = (props.start != props.item.data.start.valueOf()); + item.start = util.convert(props.item.data.start, dataset.convert['start']); + } + if ('end' in props.item.data) { + changed = changed || (props.end != props.item.data.end.valueOf()); + item.end = util.convert(props.item.data.end, dataset.convert['end']); + } + + // only apply changes when start or end is actually changed + if (changed) { + me.options.onMove(item, function (item) { + if (item) { + // apply changes + changes.push(item); + } + else { + // restore original values + if ('start' in props) props.item.data.start = props.start; + if ('end' in props) props.item.data.end = props.end; + me.requestReflow(); + } + }); + } + }); + this.touchParams.itemProps = null; + + // apply the changes to the data (if there are changes) + if (changes.length) { + dataset.update(changes); + } + + event.stopPropagation(); + } +}; + +/** + * Find an item from an event target: + * searches for the attribute 'timeline-item' in the event target's element tree + * @param {Event} event + * @return {Item | null} item + */ +ItemSet.itemFromTarget = function itemFromTarget (event) { + var target = event.target; + while (target) { + if (target.hasOwnProperty('timeline-item')) { + return target['timeline-item']; + } + target = target.parentNode; + } + + return null; +}; + +/** + * Find the ItemSet from an event target: + * searches for the attribute 'timeline-itemset' in the event target's element tree + * @param {Event} event + * @return {ItemSet | null} item + */ +ItemSet.itemSetFromTarget = function itemSetFromTarget (event) { + var target = event.target; + while (target) { + if (target.hasOwnProperty('timeline-itemset')) { + return target['timeline-itemset']; + } + target = target.parentNode; + } + + return null; +}; + +/** + * Find the DataSet to which this ItemSet is connected + * @returns {null | DataSet} dataset + * @private + */ +ItemSet.prototype._myDataSet = function _myDataSet() { + // find the root DataSet + var dataset = this.itemsData; + while (dataset instanceof DataView) { + dataset = dataset.data; + } + return dataset; +}; +/** + * @constructor Item + * @param {ItemSet} parent + * @param {Object} data Object containing (optional) parameters type, + * start, end, content, group, className. + * @param {Object} [options] Options to set initial property values + * @param {Object} [defaultOptions] default options + * // TODO: describe available options + */ +function Item (parent, data, options, defaultOptions) { + this.parent = parent; + this.data = data; + this.dom = null; + this.options = options || {}; + this.defaultOptions = defaultOptions || {}; + + this.selected = false; + this.visible = false; + this.top = 0; + this.left = 0; + this.width = 0; + this.height = 0; + this.offset = 0; +} + +/** + * Select current item + */ +Item.prototype.select = function select() { + this.selected = true; + if (this.visible) this.repaint(); +}; + +/** + * Unselect current item + */ +Item.prototype.unselect = function unselect() { + this.selected = false; + if (this.visible) this.repaint(); +}; + +/** + * Show the Item in the DOM (when not already visible) + * @return {Boolean} changed + */ +Item.prototype.show = function show() { + return false; +}; + +/** + * Hide the Item from the DOM (when visible) + * @return {Boolean} changed + */ +Item.prototype.hide = function hide() { + return false; +}; + +/** + * Repaint the item + * @return {Boolean} changed + */ +Item.prototype.repaint = function repaint() { + // should be implemented by the item + return false; +}; + +/** + * Reflow the item + * @return {Boolean} resized + */ +Item.prototype.reflow = function reflow() { + // should be implemented by the item + return false; +}; + +/** + * Give the item a display offset in pixels + * @param {Number} offset Offset on screen in pixels + */ +Item.prototype.setOffset = function setOffset(offset) { + this.offset = offset; +}; + +/** + * Repaint a delete button on the top right of the item when the item is selected + * @param {HTMLElement} anchor + * @private + */ +Item.prototype._repaintDeleteButton = function (anchor) { + if (this.selected && this.options.editable && !this.dom.deleteButton) { + // create and show button + var parent = this.parent; + var id = this.id; + + var deleteButton = document.createElement('div'); + deleteButton.className = 'delete'; + deleteButton.title = 'Delete this item'; + + Hammer(deleteButton, { + preventDefault: true + }).on('tap', function (event) { + parent.removeItem(id); + event.stopPropagation(); + }); + + anchor.appendChild(deleteButton); + this.dom.deleteButton = deleteButton; + } + else if (!this.selected && this.dom.deleteButton) { + // remove button + if (this.dom.deleteButton.parentNode) { + this.dom.deleteButton.parentNode.removeChild(this.dom.deleteButton); + } + this.dom.deleteButton = null; + } +}; + +/** + * @constructor ItemBox + * @extends Item + * @param {ItemSet} parent + * @param {Object} data Object containing parameters start + * content, className. + * @param {Object} [options] Options to set initial property values + * @param {Object} [defaultOptions] default options + * // TODO: describe available options + */ +function ItemBox (parent, data, options, defaultOptions) { + this.props = { + dot: { + left: 0, + top: 0, + width: 0, + height: 0 + }, + line: { + top: 0, + left: 0, + width: 0, + height: 0 + } + }; + + Item.call(this, parent, data, options, defaultOptions); +} + +ItemBox.prototype = new Item (null, null); + +/** + * Repaint the item + * @return {Boolean} changed + */ +ItemBox.prototype.repaint = function repaint() { + // TODO: make an efficient repaint + var changed = false; + var dom = this.dom; + + if (!dom) { + this._create(); + dom = this.dom; + changed = true; + } + + if (dom) { + if (!this.parent) { + throw new Error('Cannot repaint item: no parent attached'); + } + + if (!dom.box.parentNode) { + var foreground = this.parent.getForeground(); + if (!foreground) { + throw new Error('Cannot repaint time axis: ' + + 'parent has no foreground container element'); + } + foreground.appendChild(dom.box); + changed = true; + } + + if (!dom.line.parentNode) { + var background = this.parent.getBackground(); + if (!background) { + throw new Error('Cannot repaint time axis: ' + + 'parent has no background container element'); + } + background.appendChild(dom.line); + changed = true; + } + + if (!dom.dot.parentNode) { + var axis = this.parent.getAxis(); + if (!background) { + throw new Error('Cannot repaint time axis: ' + + 'parent has no axis container element'); + } + axis.appendChild(dom.dot); + changed = true; + } + + this._repaintDeleteButton(dom.box); + + // update contents + if (this.data.content != this.content) { + this.content = this.data.content; + if (this.content instanceof Element) { + dom.content.innerHTML = ''; + dom.content.appendChild(this.content); + } + else if (this.data.content != undefined) { + dom.content.innerHTML = this.content; + } + else { + throw new Error('Property "content" missing in item ' + this.data.id); + } + changed = true; + } + + // update class + var className = (this.data.className? ' ' + this.data.className : '') + + (this.selected ? ' selected' : ''); + if (this.className != className) { + this.className = className; + dom.box.className = 'item box' + className; + dom.line.className = 'item line' + className; + dom.dot.className = 'item dot' + className; + changed = true; + } + } + + return changed; +}; + +/** + * Show the item in the DOM (when not already visible). The items DOM will + * be created when needed. + * @return {Boolean} changed + */ +ItemBox.prototype.show = function show() { + if (!this.dom || !this.dom.box.parentNode) { + return this.repaint(); + } + else { + return false; + } +}; + +/** + * Hide the item from the DOM (when visible) + * @return {Boolean} changed + */ +ItemBox.prototype.hide = function hide() { + var changed = false, + dom = this.dom; + if (dom) { + if (dom.box.parentNode) { + dom.box.parentNode.removeChild(dom.box); + changed = true; + } + if (dom.line.parentNode) { + dom.line.parentNode.removeChild(dom.line); + } + if (dom.dot.parentNode) { + dom.dot.parentNode.removeChild(dom.dot); + } + } + return changed; +}; + +/** + * Reflow the item: calculate its actual size and position from the DOM + * @return {boolean} resized returns true if the axis is resized + * @override + */ +ItemBox.prototype.reflow = function reflow() { + var changed = 0, + update, + dom, + props, + options, + margin, + start, + align, + orientation, + top, + left, + data, + range; + + if (this.data.start == undefined) { + throw new Error('Property "start" missing in item ' + this.data.id); + } + + data = this.data; + range = this.parent && this.parent.range; + if (data && range) { + // TODO: account for the width of the item + var interval = (range.end - range.start); + this.visible = (data.start > range.start - interval) && (data.start < range.end + interval); + } + else { + this.visible = false; + } + + if (this.visible) { + dom = this.dom; + if (dom) { + update = util.updateProperty; + props = this.props; + options = this.options; + start = this.parent.toScreen(this.data.start) + this.offset; + align = options.align || this.defaultOptions.align; + margin = options.margin && options.margin.axis || this.defaultOptions.margin.axis; + orientation = options.orientation || this.defaultOptions.orientation; + + changed += update(props.dot, 'height', dom.dot.offsetHeight); + changed += update(props.dot, 'width', dom.dot.offsetWidth); + changed += update(props.line, 'width', dom.line.offsetWidth); + changed += update(props.line, 'height', dom.line.offsetHeight); + changed += update(props.line, 'top', dom.line.offsetTop); + changed += update(this, 'width', dom.box.offsetWidth); + changed += update(this, 'height', dom.box.offsetHeight); + if (align == 'right') { + left = start - this.width; + } + else if (align == 'left') { + left = start; + } + else { + // default or 'center' + left = start - this.width / 2; + } + changed += update(this, 'left', left); + + changed += update(props.line, 'left', start - props.line.width / 2); + changed += update(props.dot, 'left', start - props.dot.width / 2); + changed += update(props.dot, 'top', -props.dot.height / 2); + if (orientation == 'top') { + top = margin; + + changed += update(this, 'top', top); + } + else { + // default or 'bottom' + var parentHeight = this.parent.height; + top = parentHeight - this.height - margin; + + changed += update(this, 'top', top); + } + } + else { + changed += 1; + } + } + + return (changed > 0); +}; + +/** + * Create an items DOM + * @private + */ +ItemBox.prototype._create = function _create() { + var dom = this.dom; + if (!dom) { + this.dom = dom = {}; + + // create the box + dom.box = document.createElement('DIV'); + // className is updated in repaint() + + // contents box (inside the background box). used for making margins + dom.content = document.createElement('DIV'); + dom.content.className = 'content'; + dom.box.appendChild(dom.content); + + // line to axis + dom.line = document.createElement('DIV'); + dom.line.className = 'line'; + + // dot on axis + dom.dot = document.createElement('DIV'); + dom.dot.className = 'dot'; + + // attach this item as attribute + dom.box['timeline-item'] = this; + } +}; + +/** + * Reposition the item, recalculate its left, top, and width, using the current + * range and size of the items itemset + * @override + */ +ItemBox.prototype.reposition = function reposition() { + var dom = this.dom, + props = this.props, + orientation = this.options.orientation || this.defaultOptions.orientation; + + if (dom) { + var box = dom.box, + line = dom.line, + dot = dom.dot; + + box.style.left = this.left + 'px'; + box.style.top = this.top + 'px'; + + line.style.left = props.line.left + 'px'; + if (orientation == 'top') { + line.style.top = 0 + 'px'; + line.style.height = this.top + 'px'; + } + else { + // orientation 'bottom' + line.style.top = (this.top + this.height) + 'px'; + line.style.height = Math.max(this.parent.height - this.top - this.height + + this.props.dot.height / 2, 0) + 'px'; + } + + dot.style.left = props.dot.left + 'px'; + dot.style.top = props.dot.top + 'px'; + } +}; + +/** + * @constructor ItemPoint + * @extends Item + * @param {ItemSet} parent + * @param {Object} data Object containing parameters start + * content, className. + * @param {Object} [options] Options to set initial property values + * @param {Object} [defaultOptions] default options + * // TODO: describe available options + */ +function ItemPoint (parent, data, options, defaultOptions) { + this.props = { + dot: { + top: 0, + width: 0, + height: 0 + }, + content: { + height: 0, + marginLeft: 0 + } + }; + + Item.call(this, parent, data, options, defaultOptions); +} + +ItemPoint.prototype = new Item (null, null); + +/** + * Repaint the item + * @return {Boolean} changed + */ +ItemPoint.prototype.repaint = function repaint() { + // TODO: make an efficient repaint + var changed = false; + var dom = this.dom; + + if (!dom) { + this._create(); + dom = this.dom; + changed = true; + } + + if (dom) { + if (!this.parent) { + throw new Error('Cannot repaint item: no parent attached'); + } + var foreground = this.parent.getForeground(); + if (!foreground) { + throw new Error('Cannot repaint time axis: ' + + 'parent has no foreground container element'); + } + + if (!dom.point.parentNode) { + foreground.appendChild(dom.point); + foreground.appendChild(dom.point); + changed = true; + } + + // update contents + if (this.data.content != this.content) { + this.content = this.data.content; + if (this.content instanceof Element) { + dom.content.innerHTML = ''; + dom.content.appendChild(this.content); + } + else if (this.data.content != undefined) { + dom.content.innerHTML = this.content; + } + else { + throw new Error('Property "content" missing in item ' + this.data.id); + } + changed = true; + } + + this._repaintDeleteButton(dom.point); + + // update class + var className = (this.data.className? ' ' + this.data.className : '') + + (this.selected ? ' selected' : ''); + if (this.className != className) { + this.className = className; + dom.point.className = 'item point' + className; + changed = true; + } + } + + return changed; +}; + +/** + * Show the item in the DOM (when not already visible). The items DOM will + * be created when needed. + * @return {Boolean} changed + */ +ItemPoint.prototype.show = function show() { + if (!this.dom || !this.dom.point.parentNode) { + return this.repaint(); + } + else { + return false; + } +}; + +/** + * Hide the item from the DOM (when visible) + * @return {Boolean} changed + */ +ItemPoint.prototype.hide = function hide() { + var changed = false, + dom = this.dom; + if (dom) { + if (dom.point.parentNode) { + dom.point.parentNode.removeChild(dom.point); + changed = true; + } + } + return changed; +}; + +/** + * Reflow the item: calculate its actual size from the DOM + * @return {boolean} resized returns true if the axis is resized + * @override + */ +ItemPoint.prototype.reflow = function reflow() { + var changed = 0, + update, + dom, + props, + options, + margin, + orientation, + start, + top, + data, + range; + + if (this.data.start == undefined) { + throw new Error('Property "start" missing in item ' + this.data.id); + } + + data = this.data; + range = this.parent && this.parent.range; + if (data && range) { + // TODO: account for the width of the item + var interval = (range.end - range.start); + this.visible = (data.start > range.start - interval) && (data.start < range.end); + } + else { + this.visible = false; + } + + if (this.visible) { + dom = this.dom; + if (dom) { + update = util.updateProperty; + props = this.props; + options = this.options; + orientation = options.orientation || this.defaultOptions.orientation; + margin = options.margin && options.margin.axis || this.defaultOptions.margin.axis; + start = this.parent.toScreen(this.data.start) + this.offset; + + changed += update(this, 'width', dom.point.offsetWidth); + changed += update(this, 'height', dom.point.offsetHeight); + changed += update(props.dot, 'width', dom.dot.offsetWidth); + changed += update(props.dot, 'height', dom.dot.offsetHeight); + changed += update(props.content, 'height', dom.content.offsetHeight); + + if (orientation == 'top') { + top = margin; + } + else { + // default or 'bottom' + var parentHeight = this.parent.height; + top = Math.max(parentHeight - this.height - margin, 0); + } + changed += update(this, 'top', top); + changed += update(this, 'left', start - props.dot.width / 2); + changed += update(props.content, 'marginLeft', 1.5 * props.dot.width); + //changed += update(props.content, 'marginRight', 0.5 * props.dot.width); // TODO + + changed += update(props.dot, 'top', (this.height - props.dot.height) / 2); + } + else { + changed += 1; + } + } + + return (changed > 0); +}; + +/** + * Create an items DOM + * @private + */ +ItemPoint.prototype._create = function _create() { + var dom = this.dom; + if (!dom) { + this.dom = dom = {}; + + // background box + dom.point = document.createElement('div'); + // className is updated in repaint() + + // contents box, right from the dot + dom.content = document.createElement('div'); + dom.content.className = 'content'; + dom.point.appendChild(dom.content); + + // dot at start + dom.dot = document.createElement('div'); + dom.dot.className = 'dot'; + dom.point.appendChild(dom.dot); + + // attach this item as attribute + dom.point['timeline-item'] = this; + } +}; + +/** + * Reposition the item, recalculate its left, top, and width, using the current + * range and size of the items itemset + * @override + */ +ItemPoint.prototype.reposition = function reposition() { + var dom = this.dom, + props = this.props; + + if (dom) { + dom.point.style.top = this.top + 'px'; + dom.point.style.left = this.left + 'px'; + + dom.content.style.marginLeft = props.content.marginLeft + 'px'; + //dom.content.style.marginRight = props.content.marginRight + 'px'; // TODO + + dom.dot.style.top = props.dot.top + 'px'; + } +}; + +/** + * @constructor ItemRange + * @extends Item + * @param {ItemSet} parent + * @param {Object} data Object containing parameters start, end + * content, className. + * @param {Object} [options] Options to set initial property values + * @param {Object} [defaultOptions] default options + * // TODO: describe available options + */ +function ItemRange (parent, data, options, defaultOptions) { + this.props = { + content: { + left: 0, + width: 0 + } + }; + + Item.call(this, parent, data, options, defaultOptions); +} + +ItemRange.prototype = new Item (null, null); + +/** + * Repaint the item + * @return {Boolean} changed + */ +ItemRange.prototype.repaint = function repaint() { + // TODO: make an efficient repaint + var changed = false; + var dom = this.dom; + + if (!dom) { + this._create(); + dom = this.dom; + changed = true; + } + + if (dom) { + if (!this.parent) { + throw new Error('Cannot repaint item: no parent attached'); + } + var foreground = this.parent.getForeground(); + if (!foreground) { + throw new Error('Cannot repaint time axis: ' + + 'parent has no foreground container element'); + } + + if (!dom.box.parentNode) { + foreground.appendChild(dom.box); + changed = true; + } + + // update content + if (this.data.content != this.content) { + this.content = this.data.content; + if (this.content instanceof Element) { + dom.content.innerHTML = ''; + dom.content.appendChild(this.content); + } + else if (this.data.content != undefined) { + dom.content.innerHTML = this.content; + } + else { + throw new Error('Property "content" missing in item ' + this.data.id); + } + changed = true; + } + + this._repaintDeleteButton(dom.box); + this._repaintDragLeft(); + this._repaintDragRight(); + + // update class + var className = (this.data.className ? (' ' + this.data.className) : '') + + (this.selected ? ' selected' : ''); + if (this.className != className) { + this.className = className; + dom.box.className = 'item range' + className; + changed = true; + } + } + + return changed; +}; + +/** + * Show the item in the DOM (when not already visible). The items DOM will + * be created when needed. + * @return {Boolean} changed + */ +ItemRange.prototype.show = function show() { + if (!this.dom || !this.dom.box.parentNode) { + return this.repaint(); + } + else { + return false; + } +}; + +/** + * Hide the item from the DOM (when visible) + * @return {Boolean} changed + */ +ItemRange.prototype.hide = function hide() { + var changed = false, + dom = this.dom; + if (dom) { + if (dom.box.parentNode) { + dom.box.parentNode.removeChild(dom.box); + changed = true; + } + } + return changed; +}; + +/** + * Reflow the item: calculate its actual size from the DOM + * @return {boolean} resized returns true if the axis is resized + * @override + */ +ItemRange.prototype.reflow = function reflow() { + var changed = 0, + dom, + props, + options, + margin, + padding, + parent, + start, + end, + data, + range, + update, + box, + parentWidth, + contentLeft, + orientation, + top; + + if (this.data.start == undefined) { + throw new Error('Property "start" missing in item ' + this.data.id); + } + if (this.data.end == undefined) { + throw new Error('Property "end" missing in item ' + this.data.id); + } + + data = this.data; + range = this.parent && this.parent.range; + if (data && range) { + // TODO: account for the width of the item. Take some margin + this.visible = (data.start < range.end) && (data.end > range.start); + } + else { + this.visible = false; + } + + if (this.visible) { + dom = this.dom; + if (dom) { + props = this.props; + options = this.options; + parent = this.parent; + start = parent.toScreen(this.data.start) + this.offset; + end = parent.toScreen(this.data.end) + this.offset; + update = util.updateProperty; + box = dom.box; + parentWidth = parent.width; + orientation = options.orientation || this.defaultOptions.orientation; + margin = options.margin && options.margin.axis || this.defaultOptions.margin.axis; + padding = options.padding || this.defaultOptions.padding; + + changed += update(props.content, 'width', dom.content.offsetWidth); + + changed += update(this, 'height', box.offsetHeight); + + // limit the width of the this, as browsers cannot draw very wide divs + if (start < -parentWidth) { + start = -parentWidth; + } + if (end > 2 * parentWidth) { + end = 2 * parentWidth; + } + + // when range exceeds left of the window, position the contents at the left of the visible area + if (start < 0) { + contentLeft = Math.min(-start, + (end - start - props.content.width - 2 * padding)); + // TODO: remove the need for options.padding. it's terrible. + } + else { + contentLeft = 0; + } + changed += update(props.content, 'left', contentLeft); + + if (orientation == 'top') { + top = margin; + changed += update(this, 'top', top); + } + else { + // default or 'bottom' + top = parent.height - this.height - margin; + changed += update(this, 'top', top); + } + + changed += update(this, 'left', start); + changed += update(this, 'width', Math.max(end - start, 1)); // TODO: reckon with border width; + } + else { + changed += 1; + } + } + + return (changed > 0); +}; + +/** + * Create an items DOM + * @private + */ +ItemRange.prototype._create = function _create() { + var dom = this.dom; + if (!dom) { + this.dom = dom = {}; + // background box + dom.box = document.createElement('div'); + // className is updated in repaint() + + // contents box + dom.content = document.createElement('div'); + dom.content.className = 'content'; + dom.box.appendChild(dom.content); + + // attach this item as attribute + dom.box['timeline-item'] = this; + } +}; + +/** + * Reposition the item, recalculate its left, top, and width, using the current + * range and size of the items itemset + * @override + */ +ItemRange.prototype.reposition = function reposition() { + var dom = this.dom, + props = this.props; + + if (dom) { + dom.box.style.top = this.top + 'px'; + dom.box.style.left = this.left + 'px'; + dom.box.style.width = this.width + 'px'; + + dom.content.style.left = props.content.left + 'px'; + } +}; + +/** + * Repaint a drag area on the left side of the range when the range is selected + * @private + */ +ItemRange.prototype._repaintDragLeft = function () { + if (this.selected && this.options.editable && !this.dom.dragLeft) { + // create and show drag area + var dragLeft = document.createElement('div'); + dragLeft.className = 'drag-left'; + dragLeft.dragLeftItem = this; + + // TODO: this should be redundant? + Hammer(dragLeft, { + preventDefault: true + }).on('drag', function () { + //console.log('drag left') + }); + + this.dom.box.appendChild(dragLeft); + this.dom.dragLeft = dragLeft; + } + else if (!this.selected && this.dom.dragLeft) { + // delete drag area + if (this.dom.dragLeft.parentNode) { + this.dom.dragLeft.parentNode.removeChild(this.dom.dragLeft); + } + this.dom.dragLeft = null; + } +}; + +/** + * Repaint a drag area on the right side of the range when the range is selected + * @private + */ +ItemRange.prototype._repaintDragRight = function () { + if (this.selected && this.options.editable && !this.dom.dragRight) { + // create and show drag area + var dragRight = document.createElement('div'); + dragRight.className = 'drag-right'; + dragRight.dragRightItem = this; + + // TODO: this should be redundant? + Hammer(dragRight, { + preventDefault: true + }).on('drag', function () { + //console.log('drag right') + }); + + this.dom.box.appendChild(dragRight); + this.dom.dragRight = dragRight; + } + else if (!this.selected && this.dom.dragRight) { + // delete drag area + if (this.dom.dragRight.parentNode) { + this.dom.dragRight.parentNode.removeChild(this.dom.dragRight); + } + this.dom.dragRight = null; + } +}; + +/** + * @constructor ItemRangeOverflow + * @extends ItemRange + * @param {ItemSet} parent + * @param {Object} data Object containing parameters start, end + * content, className. + * @param {Object} [options] Options to set initial property values + * @param {Object} [defaultOptions] default options + * // TODO: describe available options + */ +function ItemRangeOverflow (parent, data, options, defaultOptions) { + this.props = { + content: { + left: 0, + width: 0 + } + }; + + // define a private property _width, which is the with of the range box + // adhering to the ranges start and end date. The property width has a + // getter which returns the max of border width and content width + this._width = 0; + Object.defineProperty(this, 'width', { + get: function () { + return (this.props.content && this._width < this.props.content.width) ? + this.props.content.width : + this._width; + }, + + set: function (width) { + this._width = width; + } + }); + + ItemRange.call(this, parent, data, options, defaultOptions); +} + +ItemRangeOverflow.prototype = new ItemRange (null, null); + +/** + * Repaint the item + * @return {Boolean} changed + */ +ItemRangeOverflow.prototype.repaint = function repaint() { + // TODO: make an efficient repaint + var changed = false; + var dom = this.dom; + + if (!dom) { + this._create(); + dom = this.dom; + changed = true; + } + + if (dom) { + if (!this.parent) { + throw new Error('Cannot repaint item: no parent attached'); + } + var foreground = this.parent.getForeground(); + if (!foreground) { + throw new Error('Cannot repaint time axis: ' + + 'parent has no foreground container element'); + } + + if (!dom.box.parentNode) { + foreground.appendChild(dom.box); + changed = true; + } + + // update content + if (this.data.content != this.content) { + this.content = this.data.content; + if (this.content instanceof Element) { + dom.content.innerHTML = ''; + dom.content.appendChild(this.content); + } + else if (this.data.content != undefined) { + dom.content.innerHTML = this.content; + } + else { + throw new Error('Property "content" missing in item ' + this.id); + } + changed = true; + } + + this._repaintDeleteButton(dom.box); + this._repaintDragLeft(); + this._repaintDragRight(); + + // update class + var className = (this.data.className? ' ' + this.data.className : '') + + (this.selected ? ' selected' : ''); + if (this.className != className) { + this.className = className; + dom.box.className = 'item rangeoverflow' + className; + changed = true; + } + } + + return changed; +}; + +/** + * Reposition the item, recalculate its left, top, and width, using the current + * range and size of the items itemset + * @override + */ +ItemRangeOverflow.prototype.reposition = function reposition() { + var dom = this.dom, + props = this.props; + + if (dom) { + dom.box.style.top = this.top + 'px'; + dom.box.style.left = this.left + 'px'; + dom.box.style.width = this._width + 'px'; + + dom.content.style.left = props.content.left + 'px'; + } +}; + +/** + * @constructor Group + * @param {GroupSet} parent + * @param {Number | String} groupId + * @param {Object} [options] Options to set initial property values + * // TODO: describe available options + * @extends Component + */ +function Group (parent, groupId, options) { + this.id = util.randomUUID(); + this.parent = parent; + + this.groupId = groupId; + this.itemset = null; // ItemSet + this.options = options || {}; + this.options.top = 0; + + this.props = { + label: { + width: 0, + height: 0 + } + }; + + this.top = 0; + this.left = 0; + this.width = 0; + this.height = 0; +} + +Group.prototype = new Component(); + +// TODO: comment +Group.prototype.setOptions = Component.prototype.setOptions; + +/** + * Get the container element of the panel, which can be used by a child to + * add its own widgets. + * @returns {HTMLElement} container + */ +Group.prototype.getContainer = function () { + return this.parent.getContainer(); +}; + +/** + * Set item set for the group. The group will create a view on the itemset, + * filtered by the groups id. + * @param {DataSet | DataView} items + */ +Group.prototype.setItems = function setItems(items) { + if (this.itemset) { + // remove current item set + this.itemset.hide(); + this.itemset.setItems(); + + this.parent.controller.remove(this.itemset); + this.itemset = null; + } + + if (items) { + var groupId = this.groupId; + + var itemsetOptions = Object.create(this.options); + this.itemset = new ItemSet(this, null, itemsetOptions); + this.itemset.setRange(this.parent.range); + + this.view = new DataView(items, { + filter: function (item) { + return item.group == groupId; + } + }); + this.itemset.setItems(this.view); + + this.parent.controller.add(this.itemset); + } +}; + +/** + * Set selected items by their id. Replaces the current selection. + * Unknown id's are silently ignored. + * @param {Array} [ids] An array with zero or more id's of the items to be + * selected. If ids is an empty array, all items will be + * unselected. + */ +Group.prototype.setSelection = function setSelection(ids) { + if (this.itemset) this.itemset.setSelection(ids); +}; + +/** + * Get the selected items by their id + * @return {Array} ids The ids of the selected items + */ +Group.prototype.getSelection = function getSelection() { + return this.itemset ? this.itemset.getSelection() : []; +}; + +/** + * Repaint the item + * @return {Boolean} changed + */ +Group.prototype.repaint = function repaint() { + return false; +}; + +/** + * Reflow the item + * @return {Boolean} resized + */ +Group.prototype.reflow = function reflow() { + var changed = 0, + update = util.updateProperty; + + changed += update(this, 'top', this.itemset ? this.itemset.top : 0); + changed += update(this, 'height', this.itemset ? this.itemset.height : 0); + + // TODO: reckon with the height of the group label + + if (this.label) { + var inner = this.label.firstChild; + changed += update(this.props.label, 'width', inner.clientWidth); + changed += update(this.props.label, 'height', inner.clientHeight); + } + else { + changed += update(this.props.label, 'width', 0); + changed += update(this.props.label, 'height', 0); + } + + return (changed > 0); +}; + +/** + * An GroupSet holds a set of groups + * @param {Component} parent + * @param {Component[]} [depends] Components on which this components depends + * (except for the parent) + * @param {Object} [options] See GroupSet.setOptions for the available + * options. + * @constructor GroupSet + * @extends Panel + */ +function GroupSet(parent, depends, options) { + this.id = util.randomUUID(); + this.parent = parent; + this.depends = depends; + + this.options = options || {}; + + this.range = null; // Range or Object {start: number, end: number} + this.itemsData = null; // DataSet with items + this.groupsData = null; // DataSet with groups + + this.groups = {}; // map with groups + + this.dom = {}; + this.props = { + labels: { + width: 0 + } + }; + + // TODO: implement right orientation of the labels + + // changes in groups are queued key/value map containing id/action + this.queue = {}; + + var me = this; + this.listeners = { + 'add': function (event, params) { + me._onAdd(params.items); + }, + 'update': function (event, params) { + me._onUpdate(params.items); + }, + 'remove': function (event, params) { + me._onRemove(params.items); + } + }; +} + +GroupSet.prototype = new Panel(); + +/** + * Set options for the GroupSet. Existing options will be extended/overwritten. + * @param {Object} [options] The following options are available: + * {String | function} groupsOrder + * TODO: describe options + */ +GroupSet.prototype.setOptions = Component.prototype.setOptions; + +GroupSet.prototype.setRange = function (range) { + // TODO: implement setRange +}; + +/** + * Set items + * @param {vis.DataSet | null} items + */ +GroupSet.prototype.setItems = function setItems(items) { + this.itemsData = items; + + for (var id in this.groups) { + if (this.groups.hasOwnProperty(id)) { + var group = this.groups[id]; + group.setItems(items); + } + } +}; + +/** + * Get items + * @return {vis.DataSet | null} items + */ +GroupSet.prototype.getItems = function getItems() { + return this.itemsData; +}; + +/** + * Set range (start and end). + * @param {Range | Object} range A Range or an object containing start and end. + */ +GroupSet.prototype.setRange = function setRange(range) { + this.range = range; +}; + +/** + * Set groups + * @param {vis.DataSet} groups + */ +GroupSet.prototype.setGroups = function setGroups(groups) { + var me = this, + ids; + + // unsubscribe from current dataset + if (this.groupsData) { + util.forEach(this.listeners, function (callback, event) { + me.groupsData.unsubscribe(event, callback); + }); + + // remove all drawn groups + ids = this.groupsData.getIds(); + this._onRemove(ids); + } + + // replace the dataset + if (!groups) { + this.groupsData = null; + } + else if (groups instanceof DataSet) { + this.groupsData = groups; + } + else { + this.groupsData = new DataSet({ + convert: { + start: 'Date', + end: 'Date' + } + }); + this.groupsData.add(groups); + } + + if (this.groupsData) { + // subscribe to new dataset + var id = this.id; + util.forEach(this.listeners, function (callback, event) { + me.groupsData.on(event, callback, id); + }); + + // draw all new groups + ids = this.groupsData.getIds(); + this._onAdd(ids); + } +}; + +/** + * Get groups + * @return {vis.DataSet | null} groups + */ +GroupSet.prototype.getGroups = function getGroups() { + return this.groupsData; +}; + +/** + * Set selected items by their id. Replaces the current selection. + * Unknown id's are silently ignored. + * @param {Array} [ids] An array with zero or more id's of the items to be + * selected. If ids is an empty array, all items will be + * unselected. + */ +GroupSet.prototype.setSelection = function setSelection(ids) { + var selection = [], + groups = this.groups; + + // iterate over each of the groups + for (var id in groups) { + if (groups.hasOwnProperty(id)) { + var group = groups[id]; + group.setSelection(ids); + } + } + + return selection; +}; + +/** + * Get the selected items by their id + * @return {Array} ids The ids of the selected items + */ +GroupSet.prototype.getSelection = function getSelection() { + var selection = [], + groups = this.groups; + + // iterate over each of the groups + for (var id in groups) { + if (groups.hasOwnProperty(id)) { + var group = groups[id]; + selection = selection.concat(group.getSelection()); + } + } + + return selection; +}; + +/** + * Repaint the component + * @return {Boolean} changed + */ +GroupSet.prototype.repaint = function repaint() { + var changed = 0, + i, id, group, label, + update = util.updateProperty, + asSize = util.option.asSize, + asElement = util.option.asElement, + options = this.options, + frame = this.dom.frame, + labels = this.dom.labels, + labelSet = this.dom.labelSet; + + // create frame + if (!this.parent) { + throw new Error('Cannot repaint groupset: no parent attached'); + } + var parentContainer = this.parent.getContainer(); + if (!parentContainer) { + throw new Error('Cannot repaint groupset: parent has no container element'); + } + if (!frame) { + frame = document.createElement('div'); + frame.className = 'groupset'; + frame['timeline-groupset'] = this; + this.dom.frame = frame; + + var className = options.className; + if (className) { + util.addClassName(frame, util.option.asString(className)); + } + + changed += 1; + } + if (!frame.parentNode) { + parentContainer.appendChild(frame); + changed += 1; + } + + // create labels + var labelContainer = asElement(options.labelContainer); + if (!labelContainer) { + throw new Error('Cannot repaint groupset: option "labelContainer" not defined'); + } + if (!labels) { + labels = document.createElement('div'); + labels.className = 'labels'; + this.dom.labels = labels; + } + if (!labelSet) { + labelSet = document.createElement('div'); + labelSet.className = 'label-set'; + labels.appendChild(labelSet); + this.dom.labelSet = labelSet; + } + if (!labels.parentNode || labels.parentNode != labelContainer) { + if (labels.parentNode) { + labels.parentNode.removeChild(labels.parentNode); + } + labelContainer.appendChild(labels); + } + + // reposition frame + changed += update(frame.style, 'height', asSize(options.height, this.height + 'px')); + changed += update(frame.style, 'top', asSize(options.top, '0px')); + changed += update(frame.style, 'left', asSize(options.left, '0px')); + changed += update(frame.style, 'width', asSize(options.width, '100%')); + + // reposition labels + changed += update(labelSet.style, 'top', asSize(options.top, '0px')); + changed += update(labelSet.style, 'height', asSize(options.height, this.height + 'px')); + + var me = this, + queue = this.queue, + groups = this.groups, + groupsData = this.groupsData; + + // show/hide added/changed/removed groups + var ids = Object.keys(queue); + if (ids.length) { + ids.forEach(function (id) { + var action = queue[id]; + var group = groups[id]; + + //noinspection FallthroughInSwitchStatementJS + switch (action) { + case 'add': + case 'update': + if (!group) { + var groupOptions = Object.create(me.options); + util.extend(groupOptions, { + height: null, + maxHeight: null + }); + + group = new Group(me, id, groupOptions); + group.setItems(me.itemsData); // attach items data + groups[id] = group; + + me.controller.add(group); + } + + // TODO: update group data + group.data = groupsData.get(id); + + delete queue[id]; + break; + + case 'remove': + if (group) { + group.setItems(); // detach items data + delete groups[id]; + + me.controller.remove(group); + } + + // update lists + delete queue[id]; + break; + + default: + console.log('Error: unknown action "' + action + '"'); + } + }); + + // the groupset depends on each of the groups + //this.depends = this.groups; // TODO: gives a circular reference through the parent + + // TODO: apply dependencies of the groupset + + // update the top positions of the groups in the correct order + var orderedGroups = this.groupsData.getIds({ + order: this.options.groupOrder + }); + for (i = 0; i < orderedGroups.length; i++) { + (function (group, prevGroup) { + var top = 0; + if (prevGroup) { + top = function () { + // TODO: top must reckon with options.maxHeight + return prevGroup.top + prevGroup.height; + } + } + group.setOptions({ + top: top + }); + })(groups[orderedGroups[i]], groups[orderedGroups[i - 1]]); + } + + // (re)create the labels + while (labelSet.firstChild) { + labelSet.removeChild(labelSet.firstChild); + } + for (i = 0; i < orderedGroups.length; i++) { + id = orderedGroups[i]; + label = this._createLabel(id); + labelSet.appendChild(label); + } + + changed++; + } + + // reposition the labels + // TODO: labels are not displayed correctly when orientation=='top' + // TODO: width of labelPanel is not immediately updated on a change in groups + for (id in groups) { + if (groups.hasOwnProperty(id)) { + group = groups[id]; + label = group.label; + if (label) { + label.style.top = group.top + 'px'; + label.style.height = group.height + 'px'; + } + } + } + + return (changed > 0); +}; + +/** + * Create a label for group with given id + * @param {Number} id + * @return {Element} label + * @private + */ +GroupSet.prototype._createLabel = function(id) { + var group = this.groups[id]; + var label = document.createElement('div'); + label.className = 'vlabel'; + var inner = document.createElement('div'); + inner.className = 'inner'; + label.appendChild(inner); + + var content = group.data && group.data.content; + if (content instanceof Element) { + inner.appendChild(content); + } + else if (content != undefined) { + inner.innerHTML = content; + } + + var className = group.data && group.data.className; + if (className) { + util.addClassName(label, className); + } + + group.label = label; // TODO: not so nice, parking labels in the group this way!!! + + return label; +}; + +/** + * Get container element + * @return {HTMLElement} container + */ +GroupSet.prototype.getContainer = function getContainer() { + return this.dom.frame; +}; + +/** + * Get the width of the group labels + * @return {Number} width + */ +GroupSet.prototype.getLabelsWidth = function getContainer() { + return this.props.labels.width; +}; + +/** + * Reflow the component + * @return {Boolean} resized + */ +GroupSet.prototype.reflow = function reflow() { + var changed = 0, + id, group, + options = this.options, + update = util.updateProperty, + asNumber = util.option.asNumber, + asSize = util.option.asSize, + frame = this.dom.frame; + + if (frame) { + var maxHeight = asNumber(options.maxHeight); + var fixedHeight = (asSize(options.height) != null); + var height; + if (fixedHeight) { + height = frame.offsetHeight; + } + else { + // height is not specified, calculate the sum of the height of all groups + height = 0; + + for (id in this.groups) { + if (this.groups.hasOwnProperty(id)) { + group = this.groups[id]; + height += group.height; + } + } + } + if (maxHeight != null) { + height = Math.min(height, maxHeight); + } + changed += update(this, 'height', height); + + changed += update(this, 'top', frame.offsetTop); + changed += update(this, 'left', frame.offsetLeft); + changed += update(this, 'width', frame.offsetWidth); + } + + // calculate the maximum width of the labels + var width = 0; + for (id in this.groups) { + if (this.groups.hasOwnProperty(id)) { + group = this.groups[id]; + var labelWidth = group.props && group.props.label && group.props.label.width || 0; + width = Math.max(width, labelWidth); + } + } + changed += update(this.props.labels, 'width', width); + + return (changed > 0); +}; + +/** + * Hide the component from the DOM + * @return {Boolean} changed + */ +GroupSet.prototype.hide = function hide() { + if (this.dom.frame && this.dom.frame.parentNode) { + this.dom.frame.parentNode.removeChild(this.dom.frame); + return true; + } + else { + return false; + } +}; + +/** + * Show the component in the DOM (when not already visible). + * A repaint will be executed when the component is not visible + * @return {Boolean} changed + */ +GroupSet.prototype.show = function show() { + if (!this.dom.frame || !this.dom.frame.parentNode) { + return this.repaint(); + } + else { + return false; + } +}; + +/** + * Handle updated groups + * @param {Number[]} ids + * @private + */ +GroupSet.prototype._onUpdate = function _onUpdate(ids) { + this._toQueue(ids, 'update'); +}; + +/** + * Handle changed groups + * @param {Number[]} ids + * @private + */ +GroupSet.prototype._onAdd = function _onAdd(ids) { + this._toQueue(ids, 'add'); +}; + +/** + * Handle removed groups + * @param {Number[]} ids + * @private + */ +GroupSet.prototype._onRemove = function _onRemove(ids) { + this._toQueue(ids, 'remove'); +}; + +/** + * Put groups in the queue to be added/updated/remove + * @param {Number[]} ids + * @param {String} action can be 'add', 'update', 'remove' + */ +GroupSet.prototype._toQueue = function _toQueue(ids, action) { + var queue = this.queue; + ids.forEach(function (id) { + queue[id] = action; + }); + + if (this.controller) { + //this.requestReflow(); + this.requestRepaint(); + } +}; + +/** + * Find the Group from an event target: + * searches for the attribute 'timeline-groupset' in the event target's element + * tree, then finds the right group in this groupset + * @param {Event} event + * @return {Group | null} group + */ +GroupSet.groupFromTarget = function groupFromTarget (event) { + var groupset, + target = event.target; + + while (target) { + if (target.hasOwnProperty('timeline-groupset')) { + groupset = target['timeline-groupset']; + break; + } + target = target.parentNode; + } + + if (groupset) { + for (var groupId in groupset.groups) { + if (groupset.groups.hasOwnProperty(groupId)) { + var group = groupset.groups[groupId]; + if (group.itemset && ItemSet.itemSetFromTarget(event) == group.itemset) { + return group; + } + } + } + } + + return null; +}; + +/** + * Create a timeline visualization + * @param {HTMLElement} container + * @param {vis.DataSet | Array | google.visualization.DataTable} [items] + * @param {Object} [options] See Timeline.setOptions for the available options. + * @constructor + */ +function Timeline (container, items, options) { + var me = this; + var now = moment().hours(0).minutes(0).seconds(0).milliseconds(0); + this.options = { + orientation: 'bottom', + autoResize: true, + editable: false, + selectable: true, + snap: null, // will be specified after timeaxis is created + + min: null, + max: null, + zoomMin: 10, // milliseconds + zoomMax: 1000 * 60 * 60 * 24 * 365 * 10000, // milliseconds + // moveable: true, // TODO: option moveable + // zoomable: true, // TODO: option zoomable + + showMinorLabels: true, + showMajorLabels: true, + showCurrentTime: false, + showCustomTime: false, + + onAdd: function (item, callback) { + callback(item); + }, + onUpdate: function (item, callback) { + callback(item); + }, + onMove: function (item, callback) { + callback(item); + }, + onRemove: function (item, callback) { + callback(item); + } + }; + + // controller + this.controller = new Controller(); + + // root panel + if (!container) { + throw new Error('No container element provided'); + } + var rootOptions = Object.create(this.options); + rootOptions.height = function () { + // TODO: change to height + if (me.options.height) { + // fixed height + return me.options.height; + } + else { + // auto height + return (me.timeaxis.height + me.content.height) + 'px'; + } + }; + this.rootPanel = new RootPanel(container, rootOptions); + this.controller.add(this.rootPanel); + + // single select (or unselect) when tapping an item + this.controller.on('tap', this._onSelectItem.bind(this)); + + // multi select when holding mouse/touch, or on ctrl+click + this.controller.on('hold', this._onMultiSelectItem.bind(this)); + + // add item on doubletap + this.controller.on('doubletap', this._onAddItem.bind(this)); + + // item panel + var itemOptions = Object.create(this.options); + itemOptions.left = function () { + return me.labelPanel.width; + }; + itemOptions.width = function () { + return me.rootPanel.width - me.labelPanel.width; + }; + itemOptions.top = null; + itemOptions.height = null; + this.itemPanel = new Panel(this.rootPanel, [], itemOptions); + this.controller.add(this.itemPanel); + + // label panel + var labelOptions = Object.create(this.options); + labelOptions.top = null; + labelOptions.left = null; + labelOptions.height = null; + labelOptions.width = function () { + if (me.content && typeof me.content.getLabelsWidth === 'function') { + return me.content.getLabelsWidth(); + } + else { + return 0; + } + }; + this.labelPanel = new Panel(this.rootPanel, [], labelOptions); + this.controller.add(this.labelPanel); + + // range + var rangeOptions = Object.create(this.options); + this.range = new Range(rangeOptions); + this.range.setRange( + now.clone().add('days', -3).valueOf(), + now.clone().add('days', 4).valueOf() + ); + + this.range.subscribe(this.controller, this.rootPanel, 'move', 'horizontal'); + this.range.subscribe(this.controller, this.rootPanel, 'zoom', 'horizontal'); + this.range.on('rangechange', function (properties) { + var force = true; + me.controller.emit('rangechange', properties); + me.controller.emit('request-reflow', force); + }); + this.range.on('rangechanged', function (properties) { + var force = true; + me.controller.emit('rangechanged', properties); + me.controller.emit('request-reflow', force); + }); + + // time axis + var timeaxisOptions = Object.create(rootOptions); + timeaxisOptions.range = this.range; + timeaxisOptions.left = null; + timeaxisOptions.top = null; + timeaxisOptions.width = '100%'; + timeaxisOptions.height = null; + this.timeaxis = new TimeAxis(this.itemPanel, [], timeaxisOptions); + this.timeaxis.setRange(this.range); + this.controller.add(this.timeaxis); + this.options.snap = this.timeaxis.snap.bind(this.timeaxis); + + // current time bar + this.currenttime = new CurrentTime(this.timeaxis, [], rootOptions); + this.controller.add(this.currenttime); + + // custom time bar + this.customtime = new CustomTime(this.timeaxis, [], rootOptions); + this.controller.add(this.customtime); + + // create groupset + this.setGroups(null); + + this.itemsData = null; // DataSet + this.groupsData = null; // DataSet + + // apply options + if (options) { + this.setOptions(options); + } + + // create itemset and groupset + if (items) { + this.setItems(items); + } +} + +/** + * Add an event listener to the timeline + * @param {String} event Available events: select, rangechange, rangechanged, + * timechange, timechanged + * @param {function} callback + */ +Timeline.prototype.on = function on (event, callback) { + this.controller.on(event, callback); +}; + +/** + * Add an event listener from the timeline + * @param {String} event + * @param {function} callback + */ +Timeline.prototype.off = function off (event, callback) { + this.controller.off(event, callback); +}; + +/** + * Set options + * @param {Object} options TODO: describe the available options + */ +Timeline.prototype.setOptions = function (options) { + util.extend(this.options, options); + + // force update of range (apply new min/max etc.) + // both start and end are optional + this.range.setRange(options.start, options.end); + + if ('editable' in options || 'selectable' in options) { + if (this.options.selectable) { + // force update of selection + this.setSelection(this.getSelection()); + } + else { + // remove selection + this.setSelection([]); + } + } + + // validate the callback functions + var validateCallback = (function (fn) { + if (!(this.options[fn] instanceof Function) || this.options[fn].length != 2) { + throw new Error('option ' + fn + ' must be a function ' + fn + '(item, callback)'); + } + }).bind(this); + ['onAdd', 'onUpdate', 'onRemove', 'onMove'].forEach(validateCallback); + + this.controller.reflow(); + this.controller.repaint(); +}; + +/** + * Set a custom time bar + * @param {Date} time + */ +Timeline.prototype.setCustomTime = function (time) { + if (!this.customtime) { + throw new Error('Cannot get custom time: Custom time bar is not enabled'); + } + + this.customtime.setCustomTime(time); +}; + +/** + * Retrieve the current custom time. + * @return {Date} customTime + */ +Timeline.prototype.getCustomTime = function() { + if (!this.customtime) { + throw new Error('Cannot get custom time: Custom time bar is not enabled'); + } + + return this.customtime.getCustomTime(); +}; + +/** + * Set items + * @param {vis.DataSet | Array | google.visualization.DataTable | null} items + */ +Timeline.prototype.setItems = function(items) { + var initialLoad = (this.itemsData == null); + + // convert to type DataSet when needed + var newDataSet; + if (!items) { + newDataSet = null; + } + else if (items instanceof DataSet) { + newDataSet = items; + } + if (!(items instanceof DataSet)) { + newDataSet = new DataSet({ + convert: { + start: 'Date', + end: 'Date' + } + }); + newDataSet.add(items); + } + + // set items + this.itemsData = newDataSet; + this.content.setItems(newDataSet); + + if (initialLoad && (this.options.start == undefined || this.options.end == undefined)) { + // apply the data range as range + var dataRange = this.getItemRange(); + + // add 5% space on both sides + var start = dataRange.min; + var end = dataRange.max; + if (start != null && end != null) { + var interval = (end.valueOf() - start.valueOf()); + if (interval <= 0) { + // prevent an empty interval + interval = 24 * 60 * 60 * 1000; // 1 day + } + start = new Date(start.valueOf() - interval * 0.05); + end = new Date(end.valueOf() + interval * 0.05); + } + + // override specified start and/or end date + if (this.options.start != undefined) { + start = util.convert(this.options.start, 'Date'); + } + if (this.options.end != undefined) { + end = util.convert(this.options.end, 'Date'); + } + + // apply range if there is a min or max available + if (start != null || end != null) { + this.range.setRange(start, end); + } + } +}; + +/** + * Set groups + * @param {vis.DataSet | Array | google.visualization.DataTable} groups + */ +Timeline.prototype.setGroups = function(groups) { + var me = this; + this.groupsData = groups; + + // switch content type between ItemSet or GroupSet when needed + var Type = this.groupsData ? GroupSet : ItemSet; + if (!(this.content instanceof Type)) { + // remove old content set + if (this.content) { + this.content.hide(); + if (this.content.setItems) { + this.content.setItems(); // disconnect from items + } + if (this.content.setGroups) { + this.content.setGroups(); // disconnect from groups + } + this.controller.remove(this.content); + } + + // create new content set + var options = Object.create(this.options); + util.extend(options, { + top: function () { + if (me.options.orientation == 'top') { + return me.timeaxis.height; + } + else { + return me.itemPanel.height - me.timeaxis.height - me.content.height; + } + }, + left: null, + width: '100%', + height: function () { + if (me.options.height) { + // fixed height + return me.itemPanel.height - me.timeaxis.height; + } + else { + // auto height + return null; + } + }, + maxHeight: function () { + // TODO: change maxHeight to be a css string like '100%' or '300px' + if (me.options.maxHeight) { + if (!util.isNumber(me.options.maxHeight)) { + throw new TypeError('Number expected for property maxHeight'); + } + return me.options.maxHeight - me.timeaxis.height; + } + else { + return null; + } + }, + labelContainer: function () { + return me.labelPanel.getContainer(); + } + }); + + this.content = new Type(this.itemPanel, [this.timeaxis], options); + if (this.content.setRange) { + this.content.setRange(this.range); + } + if (this.content.setItems) { + this.content.setItems(this.itemsData); + } + if (this.content.setGroups) { + this.content.setGroups(this.groupsData); + } + this.controller.add(this.content); + } +}; + +/** + * Get the data range of the item set. + * @returns {{min: Date, max: Date}} range A range with a start and end Date. + * When no minimum is found, min==null + * When no maximum is found, max==null + */ +Timeline.prototype.getItemRange = function getItemRange() { + // calculate min from start filed + var itemsData = this.itemsData, + min = null, + max = null; + + if (itemsData) { + // calculate the minimum value of the field 'start' + var minItem = itemsData.min('start'); + min = minItem ? minItem.start.valueOf() : null; + + // calculate maximum value of fields 'start' and 'end' + var maxStartItem = itemsData.max('start'); + if (maxStartItem) { + max = maxStartItem.start.valueOf(); + } + var maxEndItem = itemsData.max('end'); + if (maxEndItem) { + if (max == null) { + max = maxEndItem.end.valueOf(); + } + else { + max = Math.max(max, maxEndItem.end.valueOf()); + } + } + } + + return { + min: (min != null) ? new Date(min) : null, + max: (max != null) ? new Date(max) : null + }; +}; + +/** + * Set selected items by their id. Replaces the current selection + * Unknown id's are silently ignored. + * @param {Array} [ids] An array with zero or more id's of the items to be + * selected. If ids is an empty array, all items will be + * unselected. + */ +Timeline.prototype.setSelection = function setSelection (ids) { + if (this.content) this.content.setSelection(ids); +}; + +/** + * Get the selected items by their id + * @return {Array} ids The ids of the selected items + */ +Timeline.prototype.getSelection = function getSelection() { + return this.content ? this.content.getSelection() : []; +}; + +/** + * Set the visible window. Both parameters are optional, you can change only + * start or only end. + * @param {Date | Number | String} [start] Start date of visible window + * @param {Date | Number | String} [end] End date of visible window + */ +Timeline.prototype.setWindow = function setWindow(start, end) { + this.range.setRange(start, end); +}; + +/** + * Get the visible window + * @return {{start: Date, end: Date}} Visible range + */ +Timeline.prototype.getWindow = function setWindow() { + var range = this.range.getRange(); + return { + start: new Date(range.start), + end: new Date(range.end) + }; +}; + +/** + * Handle selecting/deselecting an item when tapping it + * @param {Event} event + * @private + */ +// TODO: move this function to ItemSet +Timeline.prototype._onSelectItem = function (event) { + if (!this.options.selectable) return; + + var ctrlKey = event.gesture.srcEvent && event.gesture.srcEvent.ctrlKey; + var shiftKey = event.gesture.srcEvent && event.gesture.srcEvent.shiftKey; + if (ctrlKey || shiftKey) { + this._onMultiSelectItem(event); + return; + } + + var item = ItemSet.itemFromTarget(event); + + var selection = item ? [item.id] : []; + this.setSelection(selection); + + this.controller.emit('select', { + items: this.getSelection() + }); + + event.stopPropagation(); +}; + +/** + * Handle creation and updates of an item on double tap + * @param event + * @private + */ +Timeline.prototype._onAddItem = function (event) { + if (!this.options.selectable) return; + if (!this.options.editable) return; + + var me = this, + item = ItemSet.itemFromTarget(event); + + if (item) { + // update item + + // execute async handler to update the item (or cancel it) + var itemData = me.itemsData.get(item.id); // get a clone of the data from the dataset + this.options.onUpdate(itemData, function (itemData) { + if (itemData) { + me.itemsData.update(itemData); + } + }); + } + else { + // add item + var xAbs = vis.util.getAbsoluteLeft(this.rootPanel.frame); + var x = event.gesture.center.pageX - xAbs; + var newItem = { + start: this.timeaxis.snap(this._toTime(x)), + content: 'new item' + }; + + var id = util.randomUUID(); + newItem[this.itemsData.fieldId] = id; + + var group = GroupSet.groupFromTarget(event); + if (group) { + newItem.group = group.groupId; + } + + // execute async handler to customize (or cancel) adding an item + this.options.onAdd(newItem, function (item) { + if (item) { + me.itemsData.add(newItem); + + // select the created item after it is repainted + me.controller.once('repaint', function () { + me.setSelection([id]); + + me.controller.emit('select', { + items: me.getSelection() + }); + }.bind(me)); + } + }); + } +}; + +/** + * Handle selecting/deselecting multiple items when holding an item + * @param {Event} event + * @private + */ +// TODO: move this function to ItemSet +Timeline.prototype._onMultiSelectItem = function (event) { + if (!this.options.selectable) return; + + var selection, + item = ItemSet.itemFromTarget(event); + + if (item) { + // multi select items + selection = this.getSelection(); // current selection + var index = selection.indexOf(item.id); + if (index == -1) { + // item is not yet selected -> select it + selection.push(item.id); + } + else { + // item is already selected -> deselect it + selection.splice(index, 1); + } + this.setSelection(selection); + + this.controller.emit('select', { + items: this.getSelection() + }); + + event.stopPropagation(); + } +}; + +/** + * Convert a position on screen (pixels) to a datetime + * @param {int} x Position on the screen in pixels + * @return {Date} time The datetime the corresponds with given position x + * @private + */ +Timeline.prototype._toTime = function _toTime(x) { + var conversion = this.range.conversion(this.content.width); + return new Date(x / conversion.scale + conversion.offset); +}; + +/** + * Convert a datetime (Date object) into a position on the screen + * @param {Date} time A date + * @return {int} x The position on the screen in pixels which corresponds + * with the given date. + * @private + */ +Timeline.prototype._toScreen = function _toScreen(time) { + var conversion = this.range.conversion(this.content.width); + return (time.valueOf() - conversion.offset) * conversion.scale; +}; + +(function(exports) { + /** + * Parse a text source containing data in DOT language into a JSON object. + * The object contains two lists: one with nodes and one with edges. + * + * DOT language reference: http://www.graphviz.org/doc/info/lang.html + * + * @param {String} data Text containing a graph in DOT-notation + * @return {Object} graph An object containing two parameters: + * {Object[]} nodes + * {Object[]} edges + */ + function parseDOT (data) { + dot = data; + return parseGraph(); + } + + // token types enumeration + var TOKENTYPE = { + NULL : 0, + DELIMITER : 1, + IDENTIFIER: 2, + UNKNOWN : 3 + }; + + // map with all delimiters + var DELIMITERS = { + '{': true, + '}': true, + '[': true, + ']': true, + ';': true, + '=': true, + ',': true, + + '->': true, + '--': true + }; + + var dot = ''; // current dot file + var index = 0; // current index in dot file + var c = ''; // current token character in expr + var token = ''; // current token + var tokenType = TOKENTYPE.NULL; // type of the token + + /** + * Get the first character from the dot file. + * The character is stored into the char c. If the end of the dot file is + * reached, the function puts an empty string in c. + */ + function first() { + index = 0; + c = dot.charAt(0); + } + + /** + * Get the next character from the dot file. + * The character is stored into the char c. If the end of the dot file is + * reached, the function puts an empty string in c. + */ + function next() { + index++; + c = dot.charAt(index); + } + + /** + * Preview the next character from the dot file. + * @return {String} cNext + */ + function nextPreview() { + return dot.charAt(index + 1); + } + + /** + * Test whether given character is alphabetic or numeric + * @param {String} c + * @return {Boolean} isAlphaNumeric + */ + var regexAlphaNumeric = /[a-zA-Z_0-9.:#]/; + function isAlphaNumeric(c) { + return regexAlphaNumeric.test(c); + } + + /** + * Merge all properties of object b into object b + * @param {Object} a + * @param {Object} b + * @return {Object} a + */ + function merge (a, b) { + if (!a) { + a = {}; + } + + if (b) { + for (var name in b) { + if (b.hasOwnProperty(name)) { + a[name] = b[name]; + } + } + } + return a; + } + + /** + * Set a value in an object, where the provided parameter name can be a + * path with nested parameters. For example: + * + * var obj = {a: 2}; + * setValue(obj, 'b.c', 3); // obj = {a: 2, b: {c: 3}} + * + * @param {Object} obj + * @param {String} path A parameter name or dot-separated parameter path, + * like "color.highlight.border". + * @param {*} value + */ + function setValue(obj, path, value) { + var keys = path.split('.'); + var o = obj; + while (keys.length) { + var key = keys.shift(); + if (keys.length) { + // this isn't the end point + if (!o[key]) { + o[key] = {}; + } + o = o[key]; + } + else { + // this is the end point + o[key] = value; + } + } + } + + /** + * Add a node to a graph object. If there is already a node with + * the same id, their attributes will be merged. + * @param {Object} graph + * @param {Object} node + */ + function addNode(graph, node) { + var i, len; + var current = null; + + // find root graph (in case of subgraph) + var graphs = [graph]; // list with all graphs from current graph to root graph + var root = graph; + while (root.parent) { + graphs.push(root.parent); + root = root.parent; + } + + // find existing node (at root level) by its id + if (root.nodes) { + for (i = 0, len = root.nodes.length; i < len; i++) { + if (node.id === root.nodes[i].id) { + current = root.nodes[i]; + break; + } + } + } + + if (!current) { + // this is a new node + current = { + id: node.id + }; + if (graph.node) { + // clone default attributes + current.attr = merge(current.attr, graph.node); + } + } + + // add node to this (sub)graph and all its parent graphs + for (i = graphs.length - 1; i >= 0; i--) { + var g = graphs[i]; + + if (!g.nodes) { + g.nodes = []; + } + if (g.nodes.indexOf(current) == -1) { + g.nodes.push(current); + } + } + + // merge attributes + if (node.attr) { + current.attr = merge(current.attr, node.attr); + } + } + + /** + * Add an edge to a graph object + * @param {Object} graph + * @param {Object} edge + */ + function addEdge(graph, edge) { + if (!graph.edges) { + graph.edges = []; + } + graph.edges.push(edge); + if (graph.edge) { + var attr = merge({}, graph.edge); // clone default attributes + edge.attr = merge(attr, edge.attr); // merge attributes + } + } + + /** + * Create an edge to a graph object + * @param {Object} graph + * @param {String | Number | Object} from + * @param {String | Number | Object} to + * @param {String} type + * @param {Object | null} attr + * @return {Object} edge + */ + function createEdge(graph, from, to, type, attr) { + var edge = { + from: from, + to: to, + type: type + }; + + if (graph.edge) { + edge.attr = merge({}, graph.edge); // clone default attributes + } + edge.attr = merge(edge.attr || {}, attr); // merge attributes + + return edge; + } + + /** + * Get next token in the current dot file. + * The token and token type are available as token and tokenType + */ + function getToken() { + tokenType = TOKENTYPE.NULL; + token = ''; + + // skip over whitespaces + while (c == ' ' || c == '\t' || c == '\n' || c == '\r') { // space, tab, enter + next(); + } + + do { + var isComment = false; + + // skip comment + if (c == '#') { + // find the previous non-space character + var i = index - 1; + while (dot.charAt(i) == ' ' || dot.charAt(i) == '\t') { + i--; + } + if (dot.charAt(i) == '\n' || dot.charAt(i) == '') { + // the # is at the start of a line, this is indeed a line comment + while (c != '' && c != '\n') { + next(); + } + isComment = true; + } + } + if (c == '/' && nextPreview() == '/') { + // skip line comment + while (c != '' && c != '\n') { + next(); + } + isComment = true; + } + if (c == '/' && nextPreview() == '*') { + // skip block comment + while (c != '') { + if (c == '*' && nextPreview() == '/') { + // end of block comment found. skip these last two characters + next(); + next(); + break; + } + else { + next(); + } + } + isComment = true; + } + + // skip over whitespaces + while (c == ' ' || c == '\t' || c == '\n' || c == '\r') { // space, tab, enter + next(); + } + } + while (isComment); + + // check for end of dot file + if (c == '') { + // token is still empty + tokenType = TOKENTYPE.DELIMITER; + return; + } + + // check for delimiters consisting of 2 characters + var c2 = c + nextPreview(); + if (DELIMITERS[c2]) { + tokenType = TOKENTYPE.DELIMITER; + token = c2; + next(); + next(); + return; + } + + // check for delimiters consisting of 1 character + if (DELIMITERS[c]) { + tokenType = TOKENTYPE.DELIMITER; + token = c; + next(); + return; + } + + // check for an identifier (number or string) + // TODO: more precise parsing of numbers/strings (and the port separator ':') + if (isAlphaNumeric(c) || c == '-') { + token += c; + next(); + + while (isAlphaNumeric(c)) { + token += c; + next(); + } + if (token == 'false') { + token = false; // convert to boolean + } + else if (token == 'true') { + token = true; // convert to boolean + } + else if (!isNaN(Number(token))) { + token = Number(token); // convert to number + } + tokenType = TOKENTYPE.IDENTIFIER; + return; + } + + // check for a string enclosed by double quotes + if (c == '"') { + next(); + while (c != '' && (c != '"' || (c == '"' && nextPreview() == '"'))) { + token += c; + if (c == '"') { // skip the escape character + next(); + } + next(); + } + if (c != '"') { + throw newSyntaxError('End of string " expected'); + } + next(); + tokenType = TOKENTYPE.IDENTIFIER; + return; + } + + // something unknown is found, wrong characters, a syntax error + tokenType = TOKENTYPE.UNKNOWN; + while (c != '') { + token += c; + next(); + } + throw new SyntaxError('Syntax error in part "' + chop(token, 30) + '"'); + } + + /** + * Parse a graph. + * @returns {Object} graph + */ + function parseGraph() { + var graph = {}; + + first(); + getToken(); + + // optional strict keyword + if (token == 'strict') { + graph.strict = true; + getToken(); + } + + // graph or digraph keyword + if (token == 'graph' || token == 'digraph') { + graph.type = token; + getToken(); + } + + // optional graph id + if (tokenType == TOKENTYPE.IDENTIFIER) { + graph.id = token; + getToken(); + } + + // open angle bracket + if (token != '{') { + throw newSyntaxError('Angle bracket { expected'); + } + getToken(); + + // statements + parseStatements(graph); + + // close angle bracket + if (token != '}') { + throw newSyntaxError('Angle bracket } expected'); + } + getToken(); + + // end of file + if (token !== '') { + throw newSyntaxError('End of file expected'); + } + getToken(); + + // remove temporary default properties + delete graph.node; + delete graph.edge; + delete graph.graph; + + return graph; + } + + /** + * Parse a list with statements. + * @param {Object} graph + */ + function parseStatements (graph) { + while (token !== '' && token != '}') { + parseStatement(graph); + if (token == ';') { + getToken(); + } + } + } + + /** + * Parse a single statement. Can be a an attribute statement, node + * statement, a series of node statements and edge statements, or a + * parameter. + * @param {Object} graph + */ + function parseStatement(graph) { + // parse subgraph + var subgraph = parseSubgraph(graph); + if (subgraph) { + // edge statements + parseEdge(graph, subgraph); + + return; + } + + // parse an attribute statement + var attr = parseAttributeStatement(graph); + if (attr) { + return; + } + + // parse node + if (tokenType != TOKENTYPE.IDENTIFIER) { + throw newSyntaxError('Identifier expected'); + } + var id = token; // id can be a string or a number + getToken(); + + if (token == '=') { + // id statement + getToken(); + if (tokenType != TOKENTYPE.IDENTIFIER) { + throw newSyntaxError('Identifier expected'); + } + graph[id] = token; + getToken(); + // TODO: implement comma separated list with "a_list: ID=ID [','] [a_list] " + } + else { + parseNodeStatement(graph, id); + } + } + + /** + * Parse a subgraph + * @param {Object} graph parent graph object + * @return {Object | null} subgraph + */ + function parseSubgraph (graph) { + var subgraph = null; + + // optional subgraph keyword + if (token == 'subgraph') { + subgraph = {}; + subgraph.type = 'subgraph'; + getToken(); + + // optional graph id + if (tokenType == TOKENTYPE.IDENTIFIER) { + subgraph.id = token; + getToken(); + } + } + + // open angle bracket + if (token == '{') { + getToken(); + + if (!subgraph) { + subgraph = {}; + } + subgraph.parent = graph; + subgraph.node = graph.node; + subgraph.edge = graph.edge; + subgraph.graph = graph.graph; + + // statements + parseStatements(subgraph); + + // close angle bracket + if (token != '}') { + throw newSyntaxError('Angle bracket } expected'); + } + getToken(); + + // remove temporary default properties + delete subgraph.node; + delete subgraph.edge; + delete subgraph.graph; + delete subgraph.parent; + + // register at the parent graph + if (!graph.subgraphs) { + graph.subgraphs = []; + } + graph.subgraphs.push(subgraph); + } + + return subgraph; + } + + /** + * parse an attribute statement like "node [shape=circle fontSize=16]". + * Available keywords are 'node', 'edge', 'graph'. + * The previous list with default attributes will be replaced + * @param {Object} graph + * @returns {String | null} keyword Returns the name of the parsed attribute + * (node, edge, graph), or null if nothing + * is parsed. + */ + function parseAttributeStatement (graph) { + // attribute statements + if (token == 'node') { + getToken(); + + // node attributes + graph.node = parseAttributeList(); + return 'node'; + } + else if (token == 'edge') { + getToken(); + + // edge attributes + graph.edge = parseAttributeList(); + return 'edge'; + } + else if (token == 'graph') { + getToken(); + + // graph attributes + graph.graph = parseAttributeList(); + return 'graph'; + } + + return null; + } + + /** + * parse a node statement + * @param {Object} graph + * @param {String | Number} id + */ + function parseNodeStatement(graph, id) { + // node statement + var node = { + id: id + }; + var attr = parseAttributeList(); + if (attr) { + node.attr = attr; + } + addNode(graph, node); + + // edge statements + parseEdge(graph, id); + } + + /** + * Parse an edge or a series of edges + * @param {Object} graph + * @param {String | Number} from Id of the from node + */ + function parseEdge(graph, from) { + while (token == '->' || token == '--') { + var to; + var type = token; + getToken(); + + var subgraph = parseSubgraph(graph); + if (subgraph) { + to = subgraph; + } + else { + if (tokenType != TOKENTYPE.IDENTIFIER) { + throw newSyntaxError('Identifier or subgraph expected'); + } + to = token; + addNode(graph, { + id: to + }); + getToken(); + } + + // parse edge attributes + var attr = parseAttributeList(); + + // create edge + var edge = createEdge(graph, from, to, type, attr); + addEdge(graph, edge); + + from = to; + } + } + + /** + * Parse a set with attributes, + * for example [label="1.000", shape=solid] + * @return {Object | null} attr + */ + function parseAttributeList() { + var attr = null; + + while (token == '[') { + getToken(); + attr = {}; + while (token !== '' && token != ']') { + if (tokenType != TOKENTYPE.IDENTIFIER) { + throw newSyntaxError('Attribute name expected'); + } + var name = token; + + getToken(); + if (token != '=') { + throw newSyntaxError('Equal sign = expected'); + } + getToken(); + + if (tokenType != TOKENTYPE.IDENTIFIER) { + throw newSyntaxError('Attribute value expected'); + } + var value = token; + setValue(attr, name, value); // name can be a path + + getToken(); + if (token ==',') { + getToken(); + } + } + + if (token != ']') { + throw newSyntaxError('Bracket ] expected'); + } + getToken(); + } + + return attr; + } + + /** + * Create a syntax error with extra information on current token and index. + * @param {String} message + * @returns {SyntaxError} err + */ + function newSyntaxError(message) { + return new SyntaxError(message + ', got "' + chop(token, 30) + '" (char ' + index + ')'); + } + + /** + * Chop off text after a maximum length + * @param {String} text + * @param {Number} maxLength + * @returns {String} + */ + function chop (text, maxLength) { + return (text.length <= maxLength) ? text : (text.substr(0, 27) + '...'); + } + + /** + * Execute a function fn for each pair of elements in two arrays + * @param {Array | *} array1 + * @param {Array | *} array2 + * @param {function} fn + */ + function forEach2(array1, array2, fn) { + if (array1 instanceof Array) { + array1.forEach(function (elem1) { + if (array2 instanceof Array) { + array2.forEach(function (elem2) { + fn(elem1, elem2); + }); + } + else { + fn(elem1, array2); + } + }); + } + else { + if (array2 instanceof Array) { + array2.forEach(function (elem2) { + fn(array1, elem2); + }); + } + else { + fn(array1, array2); + } + } + } + + /** + * Convert a string containing a graph in DOT language into a map containing + * with nodes and edges in the format of graph. + * @param {String} data Text containing a graph in DOT-notation + * @return {Object} graphData + */ + function DOTToGraph (data) { + // parse the DOT file + var dotData = parseDOT(data); + var graphData = { + nodes: [], + edges: [], + options: {} + }; + + // copy the nodes + if (dotData.nodes) { + dotData.nodes.forEach(function (dotNode) { + var graphNode = { + id: dotNode.id, + label: String(dotNode.label || dotNode.id) + }; + merge(graphNode, dotNode.attr); + if (graphNode.image) { + graphNode.shape = 'image'; + } + graphData.nodes.push(graphNode); + }); + } + + // copy the edges + if (dotData.edges) { + /** + * Convert an edge in DOT format to an edge with VisGraph format + * @param {Object} dotEdge + * @returns {Object} graphEdge + */ + function convertEdge(dotEdge) { + var graphEdge = { + from: dotEdge.from, + to: dotEdge.to + }; + merge(graphEdge, dotEdge.attr); + graphEdge.style = (dotEdge.type == '->') ? 'arrow' : 'line'; + return graphEdge; + } + + dotData.edges.forEach(function (dotEdge) { + var from, to; + if (dotEdge.from instanceof Object) { + from = dotEdge.from.nodes; + } + else { + from = { + id: dotEdge.from + } + } + + if (dotEdge.to instanceof Object) { + to = dotEdge.to.nodes; + } + else { + to = { + id: dotEdge.to + } + } + + if (dotEdge.from instanceof Object && dotEdge.from.edges) { + dotEdge.from.edges.forEach(function (subEdge) { + var graphEdge = convertEdge(subEdge); + graphData.edges.push(graphEdge); + }); + } + + forEach2(from, to, function (from, to) { + var subEdge = createEdge(graphData, from.id, to.id, dotEdge.type, dotEdge.attr); + var graphEdge = convertEdge(subEdge); + graphData.edges.push(graphEdge); + }); + + if (dotEdge.to instanceof Object && dotEdge.to.edges) { + dotEdge.to.edges.forEach(function (subEdge) { + var graphEdge = convertEdge(subEdge); + graphData.edges.push(graphEdge); + }); + } + }); + } + + // copy the options + if (dotData.attr) { + graphData.options = dotData.attr; + } + + return graphData; + } + + // exports + exports.parseDOT = parseDOT; + exports.DOTToGraph = DOTToGraph; + +})(typeof util !== 'undefined' ? util : exports); + +/** + * Canvas shapes used by the Graph + */ +if (typeof CanvasRenderingContext2D !== 'undefined') { + + /** + * Draw a circle shape + */ + CanvasRenderingContext2D.prototype.circle = function(x, y, r) { + this.beginPath(); + this.arc(x, y, r, 0, 2*Math.PI, false); + }; + + /** + * Draw a square shape + * @param {Number} x horizontal center + * @param {Number} y vertical center + * @param {Number} r size, width and height of the square + */ + CanvasRenderingContext2D.prototype.square = function(x, y, r) { + this.beginPath(); + this.rect(x - r, y - r, r * 2, r * 2); + }; + + /** + * Draw a triangle shape + * @param {Number} x horizontal center + * @param {Number} y vertical center + * @param {Number} r radius, half the length of the sides of the triangle + */ + CanvasRenderingContext2D.prototype.triangle = function(x, y, r) { + // http://en.wikipedia.org/wiki/Equilateral_triangle + this.beginPath(); + + var s = r * 2; + var s2 = s / 2; + var ir = Math.sqrt(3) / 6 * s; // radius of inner circle + var h = Math.sqrt(s * s - s2 * s2); // height + + this.moveTo(x, y - (h - ir)); + this.lineTo(x + s2, y + ir); + this.lineTo(x - s2, y + ir); + this.lineTo(x, y - (h - ir)); + this.closePath(); + }; + + /** + * Draw a triangle shape in downward orientation + * @param {Number} x horizontal center + * @param {Number} y vertical center + * @param {Number} r radius + */ + CanvasRenderingContext2D.prototype.triangleDown = function(x, y, r) { + // http://en.wikipedia.org/wiki/Equilateral_triangle + this.beginPath(); + + var s = r * 2; + var s2 = s / 2; + var ir = Math.sqrt(3) / 6 * s; // radius of inner circle + var h = Math.sqrt(s * s - s2 * s2); // height + + this.moveTo(x, y + (h - ir)); + this.lineTo(x + s2, y - ir); + this.lineTo(x - s2, y - ir); + this.lineTo(x, y + (h - ir)); + this.closePath(); + }; + + /** + * Draw a star shape, a star with 5 points + * @param {Number} x horizontal center + * @param {Number} y vertical center + * @param {Number} r radius, half the length of the sides of the triangle + */ + CanvasRenderingContext2D.prototype.star = function(x, y, r) { + // http://www.html5canvastutorials.com/labs/html5-canvas-star-spinner/ + this.beginPath(); + + for (var n = 0; n < 10; n++) { + var radius = (n % 2 === 0) ? r * 1.3 : r * 0.5; + this.lineTo( + x + radius * Math.sin(n * 2 * Math.PI / 10), + y - radius * Math.cos(n * 2 * Math.PI / 10) + ); + } + + this.closePath(); + }; + + /** + * http://stackoverflow.com/questions/1255512/how-to-draw-a-rounded-rectangle-on-html-canvas + */ + CanvasRenderingContext2D.prototype.roundRect = function(x, y, w, h, r) { + var r2d = Math.PI/180; + if( w - ( 2 * r ) < 0 ) { r = ( w / 2 ); } //ensure that the radius isn't too large for x + if( h - ( 2 * r ) < 0 ) { r = ( h / 2 ); } //ensure that the radius isn't too large for y + this.beginPath(); + this.moveTo(x+r,y); + this.lineTo(x+w-r,y); + this.arc(x+w-r,y+r,r,r2d*270,r2d*360,false); + this.lineTo(x+w,y+h-r); + this.arc(x+w-r,y+h-r,r,0,r2d*90,false); + this.lineTo(x+r,y+h); + this.arc(x+r,y+h-r,r,r2d*90,r2d*180,false); + this.lineTo(x,y+r); + this.arc(x+r,y+r,r,r2d*180,r2d*270,false); + }; + + /** + * http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + */ + CanvasRenderingContext2D.prototype.ellipse = function(x, y, w, h) { + var kappa = .5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + this.beginPath(); + this.moveTo(x, ym); + this.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + this.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + this.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + this.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + }; + + + + /** + * http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + */ + CanvasRenderingContext2D.prototype.database = function(x, y, w, h) { + var f = 1/3; + var wEllipse = w; + var hEllipse = h * f; + + var kappa = .5522848, + ox = (wEllipse / 2) * kappa, // control point offset horizontal + oy = (hEllipse / 2) * kappa, // control point offset vertical + xe = x + wEllipse, // x-end + ye = y + hEllipse, // y-end + xm = x + wEllipse / 2, // x-middle + ym = y + hEllipse / 2, // y-middle + ymb = y + (h - hEllipse/2), // y-midlle, bottom ellipse + yeb = y + h; // y-end, bottom ellipse + + this.beginPath(); + this.moveTo(xe, ym); + + this.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + this.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + this.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + this.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + + this.lineTo(xe, ymb); + + this.bezierCurveTo(xe, ymb + oy, xm + ox, yeb, xm, yeb); + this.bezierCurveTo(xm - ox, yeb, x, ymb + oy, x, ymb); + + this.lineTo(x, ym); + }; + + + /** + * Draw an arrow point (no line) + */ + CanvasRenderingContext2D.prototype.arrow = function(x, y, angle, length) { + // tail + var xt = x - length * Math.cos(angle); + var yt = y - length * Math.sin(angle); + + // inner tail + // TODO: allow to customize different shapes + var xi = x - length * 0.9 * Math.cos(angle); + var yi = y - length * 0.9 * Math.sin(angle); + + // left + var xl = xt + length / 3 * Math.cos(angle + 0.5 * Math.PI); + var yl = yt + length / 3 * Math.sin(angle + 0.5 * Math.PI); + + // right + var xr = xt + length / 3 * Math.cos(angle - 0.5 * Math.PI); + var yr = yt + length / 3 * Math.sin(angle - 0.5 * Math.PI); + + this.beginPath(); + this.moveTo(x, y); + this.lineTo(xl, yl); + this.lineTo(xi, yi); + this.lineTo(xr, yr); + this.closePath(); + }; + + /** + * Sets up the dashedLine functionality for drawing + * Original code came from http://stackoverflow.com/questions/4576724/dotted-stroke-in-canvas + * @author David Jordan + * @date 2012-08-08 + */ + CanvasRenderingContext2D.prototype.dashedLine = function(x,y,x2,y2,dashArray){ + if (!dashArray) dashArray=[10,5]; + if (dashLength==0) dashLength = 0.001; // Hack for Safari + var dashCount = dashArray.length; + this.moveTo(x, y); + var dx = (x2-x), dy = (y2-y); + var slope = dy/dx; + var distRemaining = Math.sqrt( dx*dx + dy*dy ); + var dashIndex=0, draw=true; + while (distRemaining>=0.1){ + var dashLength = dashArray[dashIndex++%dashCount]; + if (dashLength > distRemaining) dashLength = distRemaining; + var xStep = Math.sqrt( dashLength*dashLength / (1 + slope*slope) ); + if (dx<0) xStep = -xStep; + x += xStep; + y += slope*xStep; + this[draw ? 'lineTo' : 'moveTo'](x,y); + distRemaining -= dashLength; + draw = !draw; + } + }; + + // TODO: add diamond shape +} + +/** + * @class Node + * A node. A node can be connected to other nodes via one or multiple edges. + * @param {object} properties An object containing properties for the node. All + * properties are optional, except for the id. + * {number} id Id of the node. Required + * {string} label Text label for the node + * {number} x Horizontal position of the node + * {number} y Vertical position of the node + * {string} shape Node shape, available: + * "database", "circle", "ellipse", + * "box", "image", "text", "dot", + * "star", "triangle", "triangleDown", + * "square" + * {string} image An image url + * {string} title An title text, can be HTML + * {anytype} group A group name or number + * @param {Graph.Images} imagelist A list with images. Only needed + * when the node has an image + * @param {Graph.Groups} grouplist A list with groups. Needed for + * retrieving group properties + * @param {Object} constants An object with default values for + * example for the color + * + */ +function Node(properties, imagelist, grouplist, constants) { + this.selected = false; + + this.edges = []; // all edges connected to this node + this.dynamicEdges = []; + this.reroutedEdges = {}; + this.group = constants.nodes.group; + + this.fontSize = constants.nodes.fontSize; + this.fontFace = constants.nodes.fontFace; + this.fontColor = constants.nodes.fontColor; + this.fontDrawThreshold = 3; + + this.color = constants.nodes.color; + + // set defaults for the properties + this.id = undefined; + this.shape = constants.nodes.shape; + this.image = constants.nodes.image; + this.x = null; + this.y = null; + this.xFixed = false; + this.yFixed = false; + this.horizontalAlignLeft = true; // these are for the navigation controls + this.verticalAlignTop = true; // these are for the navigation controls + this.radius = constants.nodes.radius; + this.baseRadiusValue = constants.nodes.radius; + this.radiusFixed = false; + this.radiusMin = constants.nodes.radiusMin; + this.radiusMax = constants.nodes.radiusMax; + this.level = -1; + + this.imagelist = imagelist; + this.grouplist = grouplist; + + // physics properties + this.fx = 0.0; // external force x + this.fy = 0.0; // external force y + this.vx = 0.0; // velocity x + this.vy = 0.0; // velocity y + this.minForce = constants.minForce; + this.damping = constants.physics.damping; + this.mass = 1; // kg + this.fixedData = {x:null,y:null}; + + this.setProperties(properties, constants); + + // creating the variables for clustering + this.resetCluster(); + this.dynamicEdgesLength = 0; + this.clusterSession = 0; + this.clusterSizeWidthFactor = constants.clustering.nodeScaling.width; + this.clusterSizeHeightFactor = constants.clustering.nodeScaling.height; + this.clusterSizeRadiusFactor = constants.clustering.nodeScaling.radius; + this.maxNodeSizeIncrements = constants.clustering.maxNodeSizeIncrements; + this.growthIndicator = 0; + + // variables to tell the node about the graph. + this.graphScaleInv = 1; + this.graphScale = 1; + this.canvasTopLeft = {"x": -300, "y": -300}; + this.canvasBottomRight = {"x": 300, "y": 300}; + this.parentEdgeId = null; +} + +/** + * (re)setting the clustering variables and objects + */ +Node.prototype.resetCluster = function() { + // clustering variables + this.formationScale = undefined; // this is used to determine when to open the cluster + this.clusterSize = 1; // this signifies the total amount of nodes in this cluster + this.containedNodes = {}; + this.containedEdges = {}; + this.clusterSessions = []; +}; + +/** + * Attach a edge to the node + * @param {Edge} edge + */ +Node.prototype.attachEdge = function(edge) { + if (this.edges.indexOf(edge) == -1) { + this.edges.push(edge); + } + if (this.dynamicEdges.indexOf(edge) == -1) { + this.dynamicEdges.push(edge); + } + this.dynamicEdgesLength = this.dynamicEdges.length; +}; + +/** + * Detach a edge from the node + * @param {Edge} edge + */ +Node.prototype.detachEdge = function(edge) { + var index = this.edges.indexOf(edge); + if (index != -1) { + this.edges.splice(index, 1); + this.dynamicEdges.splice(index, 1); + } + this.dynamicEdgesLength = this.dynamicEdges.length; +}; + + +/** + * Set or overwrite properties for the node + * @param {Object} properties an object with properties + * @param {Object} constants and object with default, global properties + */ +Node.prototype.setProperties = function(properties, constants) { + if (!properties) { + return; + } + this.originalLabel = undefined; + // basic properties + if (properties.id !== undefined) {this.id = properties.id;} + if (properties.label !== undefined) {this.label = properties.label; this.originalLabel = properties.label;} + if (properties.title !== undefined) {this.title = properties.title;} + if (properties.group !== undefined) {this.group = properties.group;} + if (properties.x !== undefined) {this.x = properties.x;} + if (properties.y !== undefined) {this.y = properties.y;} + if (properties.value !== undefined) {this.value = properties.value;} + if (properties.level !== undefined) {this.level = properties.level;} + + + // physics + if (properties.mass !== undefined) {this.mass = properties.mass;} + + // navigation controls properties + if (properties.horizontalAlignLeft !== undefined) {this.horizontalAlignLeft = properties.horizontalAlignLeft;} + if (properties.verticalAlignTop !== undefined) {this.verticalAlignTop = properties.verticalAlignTop;} + if (properties.triggerFunction !== undefined) {this.triggerFunction = properties.triggerFunction;} + + if (this.id === undefined) { + throw "Node must have an id"; + } + + // copy group properties + if (this.group) { + var groupObj = this.grouplist.get(this.group); + for (var prop in groupObj) { + if (groupObj.hasOwnProperty(prop)) { + this[prop] = groupObj[prop]; + } + } + } + + // individual shape properties + if (properties.shape !== undefined) {this.shape = properties.shape;} + if (properties.image !== undefined) {this.image = properties.image;} + if (properties.radius !== undefined) {this.radius = properties.radius;} + if (properties.color !== undefined) {this.color = Node.parseColor(properties.color);} + + if (properties.fontColor !== undefined) {this.fontColor = properties.fontColor;} + if (properties.fontSize !== undefined) {this.fontSize = properties.fontSize;} + if (properties.fontFace !== undefined) {this.fontFace = properties.fontFace;} + + if (this.image !== undefined && this.image != "") { + if (this.imagelist) { + this.imageObj = this.imagelist.load(this.image); + } + else { + throw "No imagelist provided"; + } + } + + this.xFixed = this.xFixed || (properties.x !== undefined && !properties.allowedToMoveX); + this.yFixed = this.yFixed || (properties.y !== undefined && !properties.allowedToMoveY); + this.radiusFixed = this.radiusFixed || (properties.radius !== undefined); + + if (this.shape == 'image') { + this.radiusMin = constants.nodes.widthMin; + this.radiusMax = constants.nodes.widthMax; + } + + // choose draw method depending on the shape + switch (this.shape) { + case 'database': this.draw = this._drawDatabase; this.resize = this._resizeDatabase; break; + case 'box': this.draw = this._drawBox; this.resize = this._resizeBox; break; + case 'circle': this.draw = this._drawCircle; this.resize = this._resizeCircle; break; + case 'ellipse': this.draw = this._drawEllipse; this.resize = this._resizeEllipse; break; + // TODO: add diamond shape + case 'image': this.draw = this._drawImage; this.resize = this._resizeImage; break; + case 'text': this.draw = this._drawText; this.resize = this._resizeText; break; + case 'dot': this.draw = this._drawDot; this.resize = this._resizeShape; break; + case 'square': this.draw = this._drawSquare; this.resize = this._resizeShape; break; + case 'triangle': this.draw = this._drawTriangle; this.resize = this._resizeShape; break; + case 'triangleDown': this.draw = this._drawTriangleDown; this.resize = this._resizeShape; break; + case 'star': this.draw = this._drawStar; this.resize = this._resizeShape; break; + default: this.draw = this._drawEllipse; this.resize = this._resizeEllipse; break; + } + // reset the size of the node, this can be changed + this._reset(); +}; + +/** + * Parse a color property into an object with border, background, and + * hightlight colors + * @param {Object | String} color + * @return {Object} colorObject + */ +Node.parseColor = function(color) { + var c; + if (util.isString(color)) { + if (util.isValidHex(color)) { + var hsv = util.hexToHSV(color); + var lighterColorHSV = {h:hsv.h,s:hsv.s * 0.45,v:Math.min(1,hsv.v * 1.05)}; + var darkerColorHSV = {h:hsv.h,s:Math.min(1,hsv.v * 1.25),v:hsv.v*0.6}; + var darkerColorHex = util.HSVToHex(darkerColorHSV.h ,darkerColorHSV.h ,darkerColorHSV.v); + var lighterColorHex = util.HSVToHex(lighterColorHSV.h,lighterColorHSV.s,lighterColorHSV.v); + + c = { + background: color, + border:darkerColorHex, + highlight: { + background:lighterColorHex, + border:darkerColorHex + } + }; + } + else { + c = { + background:color, + border:color, + highlight: { + background:color, + border:color + } + }; + } + } + else { + c = {}; + c.background = color.background || 'white'; + c.border = color.border || c.background; + + if (util.isString(color.highlight)) { + c.highlight = { + border: color.highlight, + background: color.highlight + } + } + else { + c.highlight = {}; + c.highlight.background = color.highlight && color.highlight.background || c.background; + c.highlight.border = color.highlight && color.highlight.border || c.border; + } + } + + return c; +}; + +/** + * select this node + */ +Node.prototype.select = function() { + this.selected = true; + this._reset(); +}; + +/** + * unselect this node + */ +Node.prototype.unselect = function() { + this.selected = false; + this._reset(); +}; + + +/** + * Reset the calculated size of the node, forces it to recalculate its size + */ +Node.prototype.clearSizeCache = function() { + this._reset(); +}; + +/** + * Reset the calculated size of the node, forces it to recalculate its size + * @private + */ +Node.prototype._reset = function() { + this.width = undefined; + this.height = undefined; +}; + +/** + * get the title of this node. + * @return {string} title The title of the node, or undefined when no title + * has been set. + */ +Node.prototype.getTitle = function() { + return this.title; +}; + +/** + * Calculate the distance to the border of the Node + * @param {CanvasRenderingContext2D} ctx + * @param {Number} angle Angle in radians + * @returns {number} distance Distance to the border in pixels + */ +Node.prototype.distanceToBorder = function (ctx, angle) { + var borderWidth = 1; + + if (!this.width) { + this.resize(ctx); + } + + switch (this.shape) { + case 'circle': + case 'dot': + return this.radius + borderWidth; + + case 'ellipse': + var a = this.width / 2; + var b = this.height / 2; + var w = (Math.sin(angle) * a); + var h = (Math.cos(angle) * b); + return a * b / Math.sqrt(w * w + h * h); + + // TODO: implement distanceToBorder for database + // TODO: implement distanceToBorder for triangle + // TODO: implement distanceToBorder for triangleDown + + case 'box': + case 'image': + case 'text': + default: + if (this.width) { + return Math.min( + Math.abs(this.width / 2 / Math.cos(angle)), + Math.abs(this.height / 2 / Math.sin(angle))) + borderWidth; + // TODO: reckon with border radius too in case of box + } + else { + return 0; + } + + } + // TODO: implement calculation of distance to border for all shapes +}; + +/** + * Set forces acting on the node + * @param {number} fx Force in horizontal direction + * @param {number} fy Force in vertical direction + */ +Node.prototype._setForce = function(fx, fy) { + this.fx = fx; + this.fy = fy; +}; + +/** + * Add forces acting on the node + * @param {number} fx Force in horizontal direction + * @param {number} fy Force in vertical direction + * @private + */ +Node.prototype._addForce = function(fx, fy) { + this.fx += fx; + this.fy += fy; +}; + +/** + * Perform one discrete step for the node + * @param {number} interval Time interval in seconds + */ +Node.prototype.discreteStep = function(interval) { + if (!this.xFixed) { + var dx = this.damping * this.vx; // damping force + var ax = (this.fx - dx) / this.mass; // acceleration + this.vx += ax * interval; // velocity + this.x += this.vx * interval; // position + } + + if (!this.yFixed) { + var dy = this.damping * this.vy; // damping force + var ay = (this.fy - dy) / this.mass; // acceleration + this.vy += ay * interval; // velocity + this.y += this.vy * interval; // position + } +}; + + + +/** + * Perform one discrete step for the node + * @param {number} interval Time interval in seconds + */ +Node.prototype.discreteStepLimited = function(interval, maxVelocity) { + if (!this.xFixed) { + var dx = this.damping * this.vx; // damping force + var ax = (this.fx - dx) / this.mass; // acceleration + this.vx += ax * interval; // velocity + this.vx = (Math.abs(this.vx) > maxVelocity) ? ((this.vx > 0) ? maxVelocity : -maxVelocity) : this.vx; + this.x += this.vx * interval; // position + } + else { + this.fx = 0; + } + + if (!this.yFixed) { + var dy = this.damping * this.vy; // damping force + var ay = (this.fy - dy) / this.mass; // acceleration + this.vy += ay * interval; // velocity + this.vy = (Math.abs(this.vy) > maxVelocity) ? ((this.vy > 0) ? maxVelocity : -maxVelocity) : this.vy; + this.y += this.vy * interval; // position + } + else { + this.fy = 0; + } +}; + +/** + * Check if this node has a fixed x and y position + * @return {boolean} true if fixed, false if not + */ +Node.prototype.isFixed = function() { + return (this.xFixed && this.yFixed); +}; + +/** + * Check if this node is moving + * @param {number} vmin the minimum velocity considered as "moving" + * @return {boolean} true if moving, false if it has no velocity + */ +// TODO: replace this method with calculating the kinetic energy +Node.prototype.isMoving = function(vmin) { + return (Math.abs(this.vx) > vmin || Math.abs(this.vy) > vmin); +}; + +/** + * check if this node is selecte + * @return {boolean} selected True if node is selected, else false + */ +Node.prototype.isSelected = function() { + return this.selected; +}; + +/** + * Retrieve the value of the node. Can be undefined + * @return {Number} value + */ +Node.prototype.getValue = function() { + return this.value; +}; + +/** + * Calculate the distance from the nodes location to the given location (x,y) + * @param {Number} x + * @param {Number} y + * @return {Number} value + */ +Node.prototype.getDistance = function(x, y) { + var dx = this.x - x, + dy = this.y - y; + return Math.sqrt(dx * dx + dy * dy); +}; + + +/** + * Adjust the value range of the node. The node will adjust it's radius + * based on its value. + * @param {Number} min + * @param {Number} max + */ +Node.prototype.setValueRange = function(min, max) { + if (!this.radiusFixed && this.value !== undefined) { + if (max == min) { + this.radius = (this.radiusMin + this.radiusMax) / 2; + } + else { + var scale = (this.radiusMax - this.radiusMin) / (max - min); + this.radius = (this.value - min) * scale + this.radiusMin; + } + } + this.baseRadiusValue = this.radius; +}; + +/** + * Draw this node in the given canvas + * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d"); + * @param {CanvasRenderingContext2D} ctx + */ +Node.prototype.draw = function(ctx) { + throw "Draw method not initialized for node"; +}; + +/** + * Recalculate the size of this node in the given canvas + * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d"); + * @param {CanvasRenderingContext2D} ctx + */ +Node.prototype.resize = function(ctx) { + throw "Resize method not initialized for node"; +}; + +/** + * Check if this object is overlapping with the provided object + * @param {Object} obj an object with parameters left, top, right, bottom + * @return {boolean} True if location is located on node + */ +Node.prototype.isOverlappingWith = function(obj) { + return (this.left < obj.right && + this.left + this.width > obj.left && + this.top < obj.bottom && + this.top + this.height > obj.top); +}; + +Node.prototype._resizeImage = function (ctx) { + // TODO: pre calculate the image size + + if (!this.width || !this.height) { // undefined or 0 + var width, height; + if (this.value) { + this.radius = this.baseRadiusValue; + var scale = this.imageObj.height / this.imageObj.width; + if (scale !== undefined) { + width = this.radius || this.imageObj.width; + height = this.radius * scale || this.imageObj.height; + } + else { + width = 0; + height = 0; + } + } + else { + width = this.imageObj.width; + height = this.imageObj.height; + } + this.width = width; + this.height = height; + + this.growthIndicator = 0; + if (this.width > 0 && this.height > 0) { + this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeWidthFactor; + this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeHeightFactor; + this.radius += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeRadiusFactor; + this.growthIndicator = this.width - width; + } + } + +}; + +Node.prototype._drawImage = function (ctx) { + this._resizeImage(ctx); + + this.left = this.x - this.width / 2; + this.top = this.y - this.height / 2; + + var yLabel; + if (this.imageObj.width != 0 ) { + // draw the shade + if (this.clusterSize > 1) { + var lineWidth = ((this.clusterSize > 1) ? 10 : 0.0); + lineWidth *= this.graphScaleInv; + lineWidth = Math.min(0.2 * this.width,lineWidth); + + ctx.globalAlpha = 0.5; + ctx.drawImage(this.imageObj, this.left - lineWidth, this.top - lineWidth, this.width + 2*lineWidth, this.height + 2*lineWidth); + } + + // draw the image + ctx.globalAlpha = 1.0; + ctx.drawImage(this.imageObj, this.left, this.top, this.width, this.height); + yLabel = this.y + this.height / 2; + } + else { + // image still loading... just draw the label for now + yLabel = this.y; + } + + this._label(ctx, this.label, this.x, yLabel, undefined, "top"); +}; + + +Node.prototype._resizeBox = function (ctx) { + if (!this.width) { + var margin = 5; + var textSize = this.getTextSize(ctx); + this.width = textSize.width + 2 * margin; + this.height = textSize.height + 2 * margin; + + this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeWidthFactor; + this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeHeightFactor; + this.growthIndicator = this.width - (textSize.width + 2 * margin); +// this.radius += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeRadiusFactor; + + } +}; + +Node.prototype._drawBox = function (ctx) { + this._resizeBox(ctx); + + this.left = this.x - this.width / 2; + this.top = this.y - this.height / 2; + + var clusterLineWidth = 2.5; + var selectionLineWidth = 2; + + ctx.strokeStyle = this.selected ? this.color.highlight.border : this.color.border; + + // draw the outer border + if (this.clusterSize > 1) { + ctx.lineWidth = (this.selected ? selectionLineWidth : 1.0) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0); + ctx.lineWidth *= this.graphScaleInv; + ctx.lineWidth = Math.min(0.1 * this.width,ctx.lineWidth); + + ctx.roundRect(this.left-2*ctx.lineWidth, this.top-2*ctx.lineWidth, this.width+4*ctx.lineWidth, this.height+4*ctx.lineWidth, this.radius); + ctx.stroke(); + } + ctx.lineWidth = (this.selected ? selectionLineWidth : 1.0) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0); + ctx.lineWidth *= this.graphScaleInv; + ctx.lineWidth = Math.min(0.1 * this.width,ctx.lineWidth); + + ctx.fillStyle = this.selected ? this.color.highlight.background : this.color.background; + + ctx.roundRect(this.left, this.top, this.width, this.height, this.radius); + ctx.fill(); + ctx.stroke(); + + this._label(ctx, this.label, this.x, this.y); +}; + + +Node.prototype._resizeDatabase = function (ctx) { + if (!this.width) { + var margin = 5; + var textSize = this.getTextSize(ctx); + var size = textSize.width + 2 * margin; + this.width = size; + this.height = size; + + // scaling used for clustering + this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeWidthFactor; + this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeHeightFactor; + this.radius += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeRadiusFactor; + this.growthIndicator = this.width - size; + } +}; + +Node.prototype._drawDatabase = function (ctx) { + this._resizeDatabase(ctx); + this.left = this.x - this.width / 2; + this.top = this.y - this.height / 2; + + var clusterLineWidth = 2.5; + var selectionLineWidth = 2; + + ctx.strokeStyle = this.selected ? this.color.highlight.border : this.color.border; + + // draw the outer border + if (this.clusterSize > 1) { + ctx.lineWidth = (this.selected ? selectionLineWidth : 1.0) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0); + ctx.lineWidth *= this.graphScaleInv; + ctx.lineWidth = Math.min(0.1 * this.width,ctx.lineWidth); + + ctx.database(this.x - this.width/2 - 2*ctx.lineWidth, this.y - this.height*0.5 - 2*ctx.lineWidth, this.width + 4*ctx.lineWidth, this.height + 4*ctx.lineWidth); + ctx.stroke(); + } + ctx.lineWidth = (this.selected ? selectionLineWidth : 1.0) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0); + ctx.lineWidth *= this.graphScaleInv; + ctx.lineWidth = Math.min(0.1 * this.width,ctx.lineWidth); + + ctx.fillStyle = this.selected ? this.color.highlight.background : this.color.background; + ctx.database(this.x - this.width/2, this.y - this.height*0.5, this.width, this.height); + ctx.fill(); + ctx.stroke(); + + this._label(ctx, this.label, this.x, this.y); +}; + + +Node.prototype._resizeCircle = function (ctx) { + if (!this.width) { + var margin = 5; + var textSize = this.getTextSize(ctx); + var diameter = Math.max(textSize.width, textSize.height) + 2 * margin; + this.radius = diameter / 2; + + this.width = diameter; + this.height = diameter; + + // scaling used for clustering +// this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeWidthFactor; +// this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeHeightFactor; + this.radius += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeRadiusFactor; + this.growthIndicator = this.radius - 0.5*diameter; + } +}; + +Node.prototype._drawCircle = function (ctx) { + this._resizeCircle(ctx); + this.left = this.x - this.width / 2; + this.top = this.y - this.height / 2; + + var clusterLineWidth = 2.5; + var selectionLineWidth = 2; + + ctx.strokeStyle = this.selected ? this.color.highlight.border : this.color.border; + + // draw the outer border + if (this.clusterSize > 1) { + ctx.lineWidth = (this.selected ? selectionLineWidth : 1.0) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0); + ctx.lineWidth *= this.graphScaleInv; + ctx.lineWidth = Math.min(0.1 * this.width,ctx.lineWidth); + + ctx.circle(this.x, this.y, this.radius+2*ctx.lineWidth); + ctx.stroke(); + } + ctx.lineWidth = (this.selected ? selectionLineWidth : 1.0) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0); + ctx.lineWidth *= this.graphScaleInv; + ctx.lineWidth = Math.min(0.1 * this.width,ctx.lineWidth); + + ctx.fillStyle = this.selected ? this.color.highlight.background : this.color.background; + ctx.circle(this.x, this.y, this.radius); + ctx.fill(); + ctx.stroke(); + + this._label(ctx, this.label, this.x, this.y); +}; + +Node.prototype._resizeEllipse = function (ctx) { + if (!this.width) { + var textSize = this.getTextSize(ctx); + + this.width = textSize.width * 1.5; + this.height = textSize.height * 2; + if (this.width < this.height) { + this.width = this.height; + } + var defaultSize = this.width; + + // scaling used for clustering + this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeWidthFactor; + this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeHeightFactor; + this.radius += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeRadiusFactor; + this.growthIndicator = this.width - defaultSize; + } +}; + +Node.prototype._drawEllipse = function (ctx) { + this._resizeEllipse(ctx); + this.left = this.x - this.width / 2; + this.top = this.y - this.height / 2; + + var clusterLineWidth = 2.5; + var selectionLineWidth = 2; + + ctx.strokeStyle = this.selected ? this.color.highlight.border : this.color.border; + + // draw the outer border + if (this.clusterSize > 1) { + ctx.lineWidth = (this.selected ? selectionLineWidth : 1.0) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0); + ctx.lineWidth *= this.graphScaleInv; + ctx.lineWidth = Math.min(0.1 * this.width,ctx.lineWidth); + + ctx.ellipse(this.left-2*ctx.lineWidth, this.top-2*ctx.lineWidth, this.width+4*ctx.lineWidth, this.height+4*ctx.lineWidth); + ctx.stroke(); + } + ctx.lineWidth = (this.selected ? selectionLineWidth : 1.0) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0); + ctx.lineWidth *= this.graphScaleInv; + ctx.lineWidth = Math.min(0.1 * this.width,ctx.lineWidth); + + ctx.fillStyle = this.selected ? this.color.highlight.background : this.color.background; + + ctx.ellipse(this.left, this.top, this.width, this.height); + ctx.fill(); + ctx.stroke(); + this._label(ctx, this.label, this.x, this.y); +}; + +Node.prototype._drawDot = function (ctx) { + this._drawShape(ctx, 'circle'); +}; + +Node.prototype._drawTriangle = function (ctx) { + this._drawShape(ctx, 'triangle'); +}; + +Node.prototype._drawTriangleDown = function (ctx) { + this._drawShape(ctx, 'triangleDown'); +}; + +Node.prototype._drawSquare = function (ctx) { + this._drawShape(ctx, 'square'); +}; + +Node.prototype._drawStar = function (ctx) { + this._drawShape(ctx, 'star'); +}; + +Node.prototype._resizeShape = function (ctx) { + if (!this.width) { + this.radius = this.baseRadiusValue; + var size = 2 * this.radius; + this.width = size; + this.height = size; + + // scaling used for clustering + this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeWidthFactor; + this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeHeightFactor; + this.radius += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeRadiusFactor; + this.growthIndicator = this.width - size; + } +}; + +Node.prototype._drawShape = function (ctx, shape) { + this._resizeShape(ctx); + + this.left = this.x - this.width / 2; + this.top = this.y - this.height / 2; + + var clusterLineWidth = 2.5; + var selectionLineWidth = 2; + var radiusMultiplier = 2; + + // choose draw method depending on the shape + switch (shape) { + case 'dot': radiusMultiplier = 2; break; + case 'square': radiusMultiplier = 2; break; + case 'triangle': radiusMultiplier = 3; break; + case 'triangleDown': radiusMultiplier = 3; break; + case 'star': radiusMultiplier = 4; break; + } + + ctx.strokeStyle = this.selected ? this.color.highlight.border : this.color.border; + + // draw the outer border + if (this.clusterSize > 1) { + ctx.lineWidth = (this.selected ? selectionLineWidth : 1.0) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0); + ctx.lineWidth *= this.graphScaleInv; + ctx.lineWidth = Math.min(0.1 * this.width,ctx.lineWidth); + + ctx[shape](this.x, this.y, this.radius + radiusMultiplier * ctx.lineWidth); + ctx.stroke(); + } + ctx.lineWidth = (this.selected ? selectionLineWidth : 1.0) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0); + ctx.lineWidth *= this.graphScaleInv; + ctx.lineWidth = Math.min(0.1 * this.width,ctx.lineWidth); + + ctx.fillStyle = this.selected ? this.color.highlight.background : this.color.background; + + ctx[shape](this.x, this.y, this.radius); + ctx.fill(); + ctx.stroke(); + + if (this.label) { + this._label(ctx, this.label, this.x, this.y + this.height / 2, undefined, 'top'); + } +}; + +Node.prototype._resizeText = function (ctx) { + if (!this.width) { + var margin = 5; + var textSize = this.getTextSize(ctx); + this.width = textSize.width + 2 * margin; + this.height = textSize.height + 2 * margin; + + // scaling used for clustering + this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeWidthFactor; + this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeHeightFactor; + this.radius += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeRadiusFactor; + this.growthIndicator = this.width - (textSize.width + 2 * margin); + } +}; + +Node.prototype._drawText = function (ctx) { + this._resizeText(ctx); + this.left = this.x - this.width / 2; + this.top = this.y - this.height / 2; + + this._label(ctx, this.label, this.x, this.y); +}; + + +Node.prototype._label = function (ctx, text, x, y, align, baseline) { + if (text && this.fontSize * this.graphScale > this.fontDrawThreshold) { + ctx.font = (this.selected ? "bold " : "") + this.fontSize + "px " + this.fontFace; + ctx.fillStyle = this.fontColor || "black"; + ctx.textAlign = align || "center"; + ctx.textBaseline = baseline || "middle"; + + var lines = text.split('\n'), + lineCount = lines.length, + fontSize = (this.fontSize + 4), + yLine = y + (1 - lineCount) / 2 * fontSize; + + for (var i = 0; i < lineCount; i++) { + ctx.fillText(lines[i], x, yLine); + yLine += fontSize; + } + } +}; + + +Node.prototype.getTextSize = function(ctx) { + if (this.label !== undefined) { + ctx.font = (this.selected ? "bold " : "") + this.fontSize + "px " + this.fontFace; + + var lines = this.label.split('\n'), + height = (this.fontSize + 4) * lines.length, + width = 0; + + for (var i = 0, iMax = lines.length; i < iMax; i++) { + width = Math.max(width, ctx.measureText(lines[i]).width); + } + + return {"width": width, "height": height}; + } + else { + return {"width": 0, "height": 0}; + } +}; + +/** + * this is used to determine if a node is visible at all. this is used to determine when it needs to be drawn. + * there is a safety margin of 0.3 * width; + * + * @returns {boolean} + */ +Node.prototype.inArea = function() { + if (this.width !== undefined) { + return (this.x + this.width*this.graphScaleInv >= this.canvasTopLeft.x && + this.x - this.width*this.graphScaleInv < this.canvasBottomRight.x && + this.y + this.height*this.graphScaleInv >= this.canvasTopLeft.y && + this.y - this.height*this.graphScaleInv < this.canvasBottomRight.y); + } + else { + return true; + } +}; + +/** + * checks if the core of the node is in the display area, this is used for opening clusters around zoom + * @returns {boolean} + */ +Node.prototype.inView = function() { + return (this.x >= this.canvasTopLeft.x && + this.x < this.canvasBottomRight.x && + this.y >= this.canvasTopLeft.y && + this.y < this.canvasBottomRight.y); +}; + +/** + * This allows the zoom level of the graph to influence the rendering + * We store the inverted scale and the coordinates of the top left, and bottom right points of the canvas + * + * @param scale + * @param canvasTopLeft + * @param canvasBottomRight + */ +Node.prototype.setScaleAndPos = function(scale,canvasTopLeft,canvasBottomRight) { + this.graphScaleInv = 1.0/scale; + this.graphScale = scale; + this.canvasTopLeft = canvasTopLeft; + this.canvasBottomRight = canvasBottomRight; +}; + + +/** + * This allows the zoom level of the graph to influence the rendering + * + * @param scale + */ +Node.prototype.setScale = function(scale) { + this.graphScaleInv = 1.0/scale; + this.graphScale = scale; +}; + + + +/** + * set the velocity at 0. Is called when this node is contained in another during clustering + */ +Node.prototype.clearVelocity = function() { + this.vx = 0; + this.vy = 0; +}; + + +/** + * Basic preservation of (kinectic) energy + * + * @param massBeforeClustering + */ +Node.prototype.updateVelocity = function(massBeforeClustering) { + var energyBefore = this.vx * this.vx * massBeforeClustering; + //this.vx = (this.vx < 0) ? -Math.sqrt(energyBefore/this.mass) : Math.sqrt(energyBefore/this.mass); + this.vx = Math.sqrt(energyBefore/this.mass); + energyBefore = this.vy * this.vy * massBeforeClustering; + //this.vy = (this.vy < 0) ? -Math.sqrt(energyBefore/this.mass) : Math.sqrt(energyBefore/this.mass); + this.vy = Math.sqrt(energyBefore/this.mass); +}; + + +/** + * @class Edge + * + * A edge connects two nodes + * @param {Object} properties Object with properties. Must contain + * At least properties from and to. + * Available properties: from (number), + * to (number), label (string, color (string), + * width (number), style (string), + * length (number), title (string) + * @param {Graph} graph A graph object, used to find and edge to + * nodes. + * @param {Object} constants An object with default values for + * example for the color + */ +function Edge (properties, graph, constants) { + if (!graph) { + throw "No graph provided"; + } + this.graph = graph; + + // initialize constants + this.widthMin = constants.edges.widthMin; + this.widthMax = constants.edges.widthMax; + + // initialize variables + this.id = undefined; + this.fromId = undefined; + this.toId = undefined; + this.style = constants.edges.style; + this.title = undefined; + this.width = constants.edges.width; + this.value = undefined; + this.length = constants.physics.springLength; + this.customLength = false; + this.selected = false; + this.smooth = constants.smoothCurves; + + this.from = null; // a node + this.to = null; // a node + this.via = null; // a temp node + + // we use this to be able to reconnect the edge to a cluster if its node is put into a cluster + // by storing the original information we can revert to the original connection when the cluser is opened. + this.originalFromId = []; + this.originalToId = []; + + this.connected = false; + + // Added to support dashed lines + // David Jordan + // 2012-08-08 + this.dash = util.extend({}, constants.edges.dash); // contains properties length, gap, altLength + + this.color = {color:constants.edges.color.color, + highlight:constants.edges.color.highlight}; + this.widthFixed = false; + this.lengthFixed = false; + + this.setProperties(properties, constants); +} + +/** + * Set or overwrite properties for the edge + * @param {Object} properties an object with properties + * @param {Object} constants and object with default, global properties + */ +Edge.prototype.setProperties = function(properties, constants) { + if (!properties) { + return; + } + + if (properties.from !== undefined) {this.fromId = properties.from;} + if (properties.to !== undefined) {this.toId = properties.to;} + + if (properties.id !== undefined) {this.id = properties.id;} + if (properties.style !== undefined) {this.style = properties.style;} + if (properties.label !== undefined) {this.label = properties.label;} + + if (this.label) { + this.fontSize = constants.edges.fontSize; + this.fontFace = constants.edges.fontFace; + this.fontColor = constants.edges.fontColor; + + if (properties.fontColor !== undefined) {this.fontColor = properties.fontColor;} + if (properties.fontSize !== undefined) {this.fontSize = properties.fontSize;} + if (properties.fontFace !== undefined) {this.fontFace = properties.fontFace;} + } + + if (properties.title !== undefined) {this.title = properties.title;} + if (properties.width !== undefined) {this.width = properties.width;} + if (properties.value !== undefined) {this.value = properties.value;} + if (properties.length !== undefined) {this.length = properties.length; + this.customLength = true;} + + // Added to support dashed lines + // David Jordan + // 2012-08-08 + if (properties.dash) { + if (properties.dash.length !== undefined) {this.dash.length = properties.dash.length;} + if (properties.dash.gap !== undefined) {this.dash.gap = properties.dash.gap;} + if (properties.dash.altLength !== undefined) {this.dash.altLength = properties.dash.altLength;} + } + + if (properties.color !== undefined) { + if (util.isString(properties.color)) { + this.color.color = properties.color; + this.color.highlight = properties.color; + } + else { + if (properties.color.color !== undefined) {this.color.color = properties.color.color;} + if (properties.color.highlight !== undefined) {this.color.highlight = properties.color.highlight;} + } + } + + // A node is connected when it has a from and to node. + this.connect(); + + this.widthFixed = this.widthFixed || (properties.width !== undefined); + this.lengthFixed = this.lengthFixed || (properties.length !== undefined); + + // set draw method based on style + switch (this.style) { + case 'line': this.draw = this._drawLine; break; + case 'arrow': this.draw = this._drawArrow; break; + case 'arrow-center': this.draw = this._drawArrowCenter; break; + case 'dash-line': this.draw = this._drawDashLine; break; + default: this.draw = this._drawLine; break; + } +}; + +/** + * Connect an edge to its nodes + */ +Edge.prototype.connect = function () { + this.disconnect(); + + this.from = this.graph.nodes[this.fromId] || null; + this.to = this.graph.nodes[this.toId] || null; + this.connected = (this.from && this.to); + + if (this.connected) { + this.from.attachEdge(this); + this.to.attachEdge(this); + } + else { + if (this.from) { + this.from.detachEdge(this); + } + if (this.to) { + this.to.detachEdge(this); + } + } +}; + +/** + * Disconnect an edge from its nodes + */ +Edge.prototype.disconnect = function () { + if (this.from) { + this.from.detachEdge(this); + this.from = null; + } + if (this.to) { + this.to.detachEdge(this); + this.to = null; + } + + this.connected = false; +}; + +/** + * get the title of this edge. + * @return {string} title The title of the edge, or undefined when no title + * has been set. + */ +Edge.prototype.getTitle = function() { + return this.title; +}; + + +/** + * Retrieve the value of the edge. Can be undefined + * @return {Number} value + */ +Edge.prototype.getValue = function() { + return this.value; +}; + +/** + * Adjust the value range of the edge. The edge will adjust it's width + * based on its value. + * @param {Number} min + * @param {Number} max + */ +Edge.prototype.setValueRange = function(min, max) { + if (!this.widthFixed && this.value !== undefined) { + var scale = (this.widthMax - this.widthMin) / (max - min); + this.width = (this.value - min) * scale + this.widthMin; + } +}; + +/** + * Redraw a edge + * Draw this edge in the given canvas + * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d"); + * @param {CanvasRenderingContext2D} ctx + */ +Edge.prototype.draw = function(ctx) { + throw "Method draw not initialized in edge"; +}; + +/** + * Check if this object is overlapping with the provided object + * @param {Object} obj an object with parameters left, top + * @return {boolean} True if location is located on the edge + */ +Edge.prototype.isOverlappingWith = function(obj) { + if (this.connected == true) { + var distMax = 10; + var xFrom = this.from.x; + var yFrom = this.from.y; + var xTo = this.to.x; + var yTo = this.to.y; + var xObj = obj.left; + var yObj = obj.top; + + var dist = this._getDistanceToEdge(xFrom, yFrom, xTo, yTo, xObj, yObj); + + return (dist < distMax); + } + else { + return false + } +}; + + +/** + * Redraw a edge as a line + * Draw this edge in the given canvas + * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d"); + * @param {CanvasRenderingContext2D} ctx + * @private + */ +Edge.prototype._drawLine = function(ctx) { + // set style + if (this.selected == true) {ctx.strokeStyle = this.color.highlight;} + else {ctx.strokeStyle = this.color.color;} + ctx.lineWidth = this._getLineWidth(); + + if (this.from != this.to) { + // draw line + this._line(ctx); + + // draw label + var point; + if (this.label) { + if (this.smooth == true) { + var midpointX = 0.5*(0.5*(this.from.x + this.via.x) + 0.5*(this.to.x + this.via.x)); + var midpointY = 0.5*(0.5*(this.from.y + this.via.y) + 0.5*(this.to.y + this.via.y)); + point = {x:midpointX, y:midpointY}; + } + else { + point = this._pointOnLine(0.5); + } + this._label(ctx, this.label, point.x, point.y); + } + } + else { + var x, y; + var radius = this.length / 4; + var node = this.from; + if (!node.width) { + node.resize(ctx); + } + if (node.width > node.height) { + x = node.x + node.width / 2; + y = node.y - radius; + } + else { + x = node.x + radius; + y = node.y - node.height / 2; + } + this._circle(ctx, x, y, radius); + point = this._pointOnCircle(x, y, radius, 0.5); + this._label(ctx, this.label, point.x, point.y); + } +}; + +/** + * Get the line width of the edge. Depends on width and whether one of the + * connected nodes is selected. + * @return {Number} width + * @private + */ +Edge.prototype._getLineWidth = function() { + if (this.selected == true) { + return Math.min(this.width * 2, this.widthMax)*this.graphScaleInv; + } + else { + return this.width*this.graphScaleInv; + } +}; + +/** + * Draw a line between two nodes + * @param {CanvasRenderingContext2D} ctx + * @private + */ +Edge.prototype._line = function (ctx) { + // draw a straight line + ctx.beginPath(); + ctx.moveTo(this.from.x, this.from.y); + if (this.smooth == true) { + ctx.quadraticCurveTo(this.via.x,this.via.y,this.to.x, this.to.y); + } + else { + ctx.lineTo(this.to.x, this.to.y); + } + ctx.stroke(); +}; + +/** + * Draw a line from a node to itself, a circle + * @param {CanvasRenderingContext2D} ctx + * @param {Number} x + * @param {Number} y + * @param {Number} radius + * @private + */ +Edge.prototype._circle = function (ctx, x, y, radius) { + // draw a circle + ctx.beginPath(); + ctx.arc(x, y, radius, 0, 2 * Math.PI, false); + ctx.stroke(); +}; + +/** + * Draw label with white background and with the middle at (x, y) + * @param {CanvasRenderingContext2D} ctx + * @param {String} text + * @param {Number} x + * @param {Number} y + * @private + */ +Edge.prototype._label = function (ctx, text, x, y) { + if (text) { + // TODO: cache the calculated size + ctx.font = ((this.from.selected || this.to.selected) ? "bold " : "") + + this.fontSize + "px " + this.fontFace; + ctx.fillStyle = 'white'; + var width = ctx.measureText(text).width; + var height = this.fontSize; + var left = x - width / 2; + var top = y - height / 2; + + ctx.fillRect(left, top, width, height); + + // draw text + ctx.fillStyle = this.fontColor || "black"; + ctx.textAlign = "left"; + ctx.textBaseline = "top"; + ctx.fillText(text, left, top); + } +}; + +/** + * Redraw a edge as a dashed line + * Draw this edge in the given canvas + * @author David Jordan + * @date 2012-08-08 + * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d"); + * @param {CanvasRenderingContext2D} ctx + * @private + */ +Edge.prototype._drawDashLine = function(ctx) { + // set style + if (this.selected == true) {ctx.strokeStyle = this.color.highlight;} + else {ctx.strokeStyle = this.color.color;} + + ctx.lineWidth = this._getLineWidth(); + + // only firefox and chrome support this method, else we use the legacy one. + if (ctx.mozDash !== undefined || ctx.setLineDash !== undefined) { + ctx.beginPath(); + ctx.moveTo(this.from.x, this.from.y); + + // configure the dash pattern + var pattern = [0]; + if (this.dash.length !== undefined && this.dash.gap !== undefined) { + pattern = [this.dash.length,this.dash.gap]; + } + else { + pattern = [5,5]; + } + + // set dash settings for chrome or firefox + if (typeof ctx.setLineDash !== 'undefined') { //Chrome + ctx.setLineDash(pattern); + ctx.lineDashOffset = 0; + + } else { //Firefox + ctx.mozDash = pattern; + ctx.mozDashOffset = 0; + } + + // draw the line + if (this.smooth == true) { + ctx.quadraticCurveTo(this.via.x,this.via.y,this.to.x, this.to.y); + } + else { + ctx.lineTo(this.to.x, this.to.y); + } + ctx.stroke(); + + // restore the dash settings. + if (typeof ctx.setLineDash !== 'undefined') { //Chrome + ctx.setLineDash([0]); + ctx.lineDashOffset = 0; + + } else { //Firefox + ctx.mozDash = [0]; + ctx.mozDashOffset = 0; + } + } + else { // unsupporting smooth lines + // draw dashed line + ctx.beginPath(); + ctx.lineCap = 'round'; + if (this.dash.altLength !== undefined) //If an alt dash value has been set add to the array this value + { + ctx.dashedLine(this.from.x,this.from.y,this.to.x,this.to.y, + [this.dash.length,this.dash.gap,this.dash.altLength,this.dash.gap]); + } + else if (this.dash.length !== undefined && this.dash.gap !== undefined) //If a dash and gap value has been set add to the array this value + { + ctx.dashedLine(this.from.x,this.from.y,this.to.x,this.to.y, + [this.dash.length,this.dash.gap]); + } + else //If all else fails draw a line + { + ctx.moveTo(this.from.x, this.from.y); + ctx.lineTo(this.to.x, this.to.y); + } + ctx.stroke(); + } + + // draw label + if (this.label) { + var point; + if (this.smooth == true) { + var midpointX = 0.5*(0.5*(this.from.x + this.via.x) + 0.5*(this.to.x + this.via.x)); + var midpointY = 0.5*(0.5*(this.from.y + this.via.y) + 0.5*(this.to.y + this.via.y)); + point = {x:midpointX, y:midpointY}; + } + else { + point = this._pointOnLine(0.5); + } + this._label(ctx, this.label, point.x, point.y); + } +}; + +/** + * Get a point on a line + * @param {Number} percentage. Value between 0 (line start) and 1 (line end) + * @return {Object} point + * @private + */ +Edge.prototype._pointOnLine = function (percentage) { + return { + x: (1 - percentage) * this.from.x + percentage * this.to.x, + y: (1 - percentage) * this.from.y + percentage * this.to.y + } +}; + +/** + * Get a point on a circle + * @param {Number} x + * @param {Number} y + * @param {Number} radius + * @param {Number} percentage. Value between 0 (line start) and 1 (line end) + * @return {Object} point + * @private + */ +Edge.prototype._pointOnCircle = function (x, y, radius, percentage) { + var angle = (percentage - 3/8) * 2 * Math.PI; + return { + x: x + radius * Math.cos(angle), + y: y - radius * Math.sin(angle) + } +}; + +/** + * Redraw a edge as a line with an arrow halfway the line + * Draw this edge in the given canvas + * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d"); + * @param {CanvasRenderingContext2D} ctx + * @private + */ +Edge.prototype._drawArrowCenter = function(ctx) { + var point; + // set style + if (this.selected == true) {ctx.strokeStyle = this.color.highlight; ctx.fillStyle = this.color.highlight;} + else {ctx.strokeStyle = this.color.color; ctx.fillStyle = this.color.color;} + ctx.lineWidth = this._getLineWidth(); + + if (this.from != this.to) { + // draw line + this._line(ctx); + + var angle = Math.atan2((this.to.y - this.from.y), (this.to.x - this.from.x)); + var length = 10 + 5 * this.width; // TODO: make customizable? + // draw an arrow halfway the line + if (this.smooth == true) { + var midpointX = 0.5*(0.5*(this.from.x + this.via.x) + 0.5*(this.to.x + this.via.x)); + var midpointY = 0.5*(0.5*(this.from.y + this.via.y) + 0.5*(this.to.y + this.via.y)); + point = {x:midpointX, y:midpointY}; + } + else { + point = this._pointOnLine(0.5); + } + + ctx.arrow(point.x, point.y, angle, length); + ctx.fill(); + ctx.stroke(); + + // draw label + if (this.label) { + this._label(ctx, this.label, point.x, point.y); + } + } + else { + // draw circle + var x, y; + var radius = 0.25 * Math.max(100,this.length); + var node = this.from; + if (!node.width) { + node.resize(ctx); + } + if (node.width > node.height) { + x = node.x + node.width * 0.5; + y = node.y - radius; + } + else { + x = node.x + radius; + y = node.y - node.height * 0.5; + } + this._circle(ctx, x, y, radius); + + // draw all arrows + var angle = 0.2 * Math.PI; + var length = 10 + 5 * this.width; // TODO: make customizable? + point = this._pointOnCircle(x, y, radius, 0.5); + ctx.arrow(point.x, point.y, angle, length); + ctx.fill(); + ctx.stroke(); + + // draw label + if (this.label) { + point = this._pointOnCircle(x, y, radius, 0.5); + this._label(ctx, this.label, point.x, point.y); + } + } +}; + + + +/** + * Redraw a edge as a line with an arrow + * Draw this edge in the given canvas + * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d"); + * @param {CanvasRenderingContext2D} ctx + * @private + */ +Edge.prototype._drawArrow = function(ctx) { + // set style + if (this.selected == true) {ctx.strokeStyle = this.color.highlight; ctx.fillStyle = this.color.highlight;} + else {ctx.strokeStyle = this.color.color; ctx.fillStyle = this.color.color;} + + ctx.lineWidth = this._getLineWidth(); + + var angle, length; + //draw a line + if (this.from != this.to) { + angle = Math.atan2((this.to.y - this.from.y), (this.to.x - this.from.x)); + var dx = (this.to.x - this.from.x); + var dy = (this.to.y - this.from.y); + var edgeSegmentLength = Math.sqrt(dx * dx + dy * dy); + + var fromBorderDist = this.from.distanceToBorder(ctx, angle + Math.PI); + var fromBorderPoint = (edgeSegmentLength - fromBorderDist) / edgeSegmentLength; + var xFrom = (fromBorderPoint) * this.from.x + (1 - fromBorderPoint) * this.to.x; + var yFrom = (fromBorderPoint) * this.from.y + (1 - fromBorderPoint) * this.to.y; + + + if (this.smooth == true) { + angle = Math.atan2((this.to.y - this.via.y), (this.to.x - this.via.x)); + dx = (this.to.x - this.via.x); + dy = (this.to.y - this.via.y); + edgeSegmentLength = Math.sqrt(dx * dx + dy * dy); + } + var toBorderDist = this.to.distanceToBorder(ctx, angle); + var toBorderPoint = (edgeSegmentLength - toBorderDist) / edgeSegmentLength; + + var xTo,yTo; + if (this.smooth == true) { + xTo = (1 - toBorderPoint) * this.via.x + toBorderPoint * this.to.x; + yTo = (1 - toBorderPoint) * this.via.y + toBorderPoint * this.to.y; + } + else { + xTo = (1 - toBorderPoint) * this.from.x + toBorderPoint * this.to.x; + yTo = (1 - toBorderPoint) * this.from.y + toBorderPoint * this.to.y; + } + + ctx.beginPath(); + ctx.moveTo(xFrom,yFrom); + if (this.smooth == true) { + ctx.quadraticCurveTo(this.via.x,this.via.y,xTo, yTo); + } + else { + ctx.lineTo(xTo, yTo); + } + ctx.stroke(); + + // draw arrow at the end of the line + length = 10 + 5 * this.width; + ctx.arrow(xTo, yTo, angle, length); + ctx.fill(); + ctx.stroke(); + + // draw label + if (this.label) { + var point; + if (this.smooth == true) { + var midpointX = 0.5*(0.5*(this.from.x + this.via.x) + 0.5*(this.to.x + this.via.x)); + var midpointY = 0.5*(0.5*(this.from.y + this.via.y) + 0.5*(this.to.y + this.via.y)); + point = {x:midpointX, y:midpointY}; + } + else { + point = this._pointOnLine(0.5); + } + this._label(ctx, this.label, point.x, point.y); + } + } + else { + // draw circle + var node = this.from; + var x, y, arrow; + var radius = 0.25 * Math.max(100,this.length); + if (!node.width) { + node.resize(ctx); + } + if (node.width > node.height) { + x = node.x + node.width * 0.5; + y = node.y - radius; + arrow = { + x: x, + y: node.y, + angle: 0.9 * Math.PI + }; + } + else { + x = node.x + radius; + y = node.y - node.height * 0.5; + arrow = { + x: node.x, + y: y, + angle: 0.6 * Math.PI + }; + } + ctx.beginPath(); + // TODO: similarly, for a line without arrows, draw to the border of the nodes instead of the center + ctx.arc(x, y, radius, 0, 2 * Math.PI, false); + ctx.stroke(); + + // draw all arrows + length = 10 + 5 * this.width; // TODO: make customizable? + ctx.arrow(arrow.x, arrow.y, arrow.angle, length); + ctx.fill(); + ctx.stroke(); + + // draw label + if (this.label) { + point = this._pointOnCircle(x, y, radius, 0.5); + this._label(ctx, this.label, point.x, point.y); + } + } +}; + + + +/** + * Calculate the distance between a point (x3,y3) and a line segment from + * (x1,y1) to (x2,y2). + * http://stackoverflow.com/questions/849211/shortest-distancae-between-a-point-and-a-line-segment + * @param {number} x1 + * @param {number} y1 + * @param {number} x2 + * @param {number} y2 + * @param {number} x3 + * @param {number} y3 + * @private + */ +Edge.prototype._getDistanceToEdge = function (x1,y1, x2,y2, x3,y3) { // x3,y3 is the point + if (this.smooth == true) { + var minDistance = 1e9; + var i,t,x,y,dx,dy; + for (i = 0; i < 10; i++) { + t = 0.1*i; + x = Math.pow(1-t,2)*x1 + (2*t*(1 - t))*this.via.x + Math.pow(t,2)*x2; + y = Math.pow(1-t,2)*y1 + (2*t*(1 - t))*this.via.y + Math.pow(t,2)*y2; + dx = Math.abs(x3-x); + dy = Math.abs(y3-y); + minDistance = Math.min(minDistance,Math.sqrt(dx*dx + dy*dy)); + } + return minDistance + } + else { + var px = x2-x1, + py = y2-y1, + something = px*px + py*py, + u = ((x3 - x1) * px + (y3 - y1) * py) / something; + + if (u > 1) { + u = 1; + } + else if (u < 0) { + u = 0; + } + + var x = x1 + u * px, + y = y1 + u * py, + dx = x - x3, + dy = y - y3; + + //# Note: If the actual distance does not matter, + //# if you only want to compare what this function + //# returns to other results of this function, you + //# can just return the squared distance instead + //# (i.e. remove the sqrt) to gain a little performance + + return Math.sqrt(dx*dx + dy*dy); + } +}; + + + +/** + * This allows the zoom level of the graph to influence the rendering + * + * @param scale + */ +Edge.prototype.setScale = function(scale) { + this.graphScaleInv = 1.0/scale; +}; + + +Edge.prototype.select = function() { + this.selected = true; +}; + +Edge.prototype.unselect = function() { + this.selected = false; +}; + +Edge.prototype.positionBezierNode = function() { + if (this.via !== null) { + this.via.x = 0.5 * (this.from.x + this.to.x); + this.via.y = 0.5 * (this.from.y + this.to.y); + } +}; +/** + * Popup is a class to create a popup window with some text + * @param {Element} container The container object. + * @param {Number} [x] + * @param {Number} [y] + * @param {String} [text] + */ +function Popup(container, x, y, text) { + if (container) { + this.container = container; + } + else { + this.container = document.body; + } + this.x = 0; + this.y = 0; + this.padding = 5; + + if (x !== undefined && y !== undefined ) { + this.setPosition(x, y); + } + if (text !== undefined) { + this.setText(text); + } + + // create the frame + this.frame = document.createElement("div"); + var style = this.frame.style; + style.position = "absolute"; + style.visibility = "hidden"; + style.border = "1px solid #666"; + style.color = "black"; + style.padding = this.padding + "px"; + style.backgroundColor = "#FFFFC6"; + style.borderRadius = "3px"; + style.MozBorderRadius = "3px"; + style.WebkitBorderRadius = "3px"; + style.boxShadow = "3px 3px 10px rgba(128, 128, 128, 0.5)"; + style.whiteSpace = "nowrap"; + this.container.appendChild(this.frame); +} + +/** + * @param {number} x Horizontal position of the popup window + * @param {number} y Vertical position of the popup window + */ +Popup.prototype.setPosition = function(x, y) { + this.x = parseInt(x); + this.y = parseInt(y); +}; + +/** + * Set the text for the popup window. This can be HTML code + * @param {string} text + */ +Popup.prototype.setText = function(text) { + this.frame.innerHTML = text; +}; + +/** + * Show the popup window + * @param {boolean} show Optional. Show or hide the window + */ +Popup.prototype.show = function (show) { + if (show === undefined) { + show = true; + } + + if (show) { + var height = this.frame.clientHeight; + var width = this.frame.clientWidth; + var maxHeight = this.frame.parentNode.clientHeight; + var maxWidth = this.frame.parentNode.clientWidth; + + var top = (this.y - height); + if (top + height + this.padding > maxHeight) { + top = maxHeight - height - this.padding; + } + if (top < this.padding) { + top = this.padding; + } + + var left = this.x; + if (left + width + this.padding > maxWidth) { + left = maxWidth - width - this.padding; + } + if (left < this.padding) { + left = this.padding; + } + + this.frame.style.left = left + "px"; + this.frame.style.top = top + "px"; + this.frame.style.visibility = "visible"; + } + else { + this.hide(); + } +}; + +/** + * Hide the popup window + */ +Popup.prototype.hide = function () { + this.frame.style.visibility = "hidden"; +}; + +/** + * @class Groups + * This class can store groups and properties specific for groups. + */ +Groups = function () { + this.clear(); + this.defaultIndex = 0; +}; + + +/** + * default constants for group colors + */ +Groups.DEFAULT = [ + {border: "#2B7CE9", background: "#97C2FC", highlight: {border: "#2B7CE9", background: "#D2E5FF"}}, // blue + {border: "#FFA500", background: "#FFFF00", highlight: {border: "#FFA500", background: "#FFFFA3"}}, // yellow + {border: "#FA0A10", background: "#FB7E81", highlight: {border: "#FA0A10", background: "#FFAFB1"}}, // red + {border: "#41A906", background: "#7BE141", highlight: {border: "#41A906", background: "#A1EC76"}}, // green + {border: "#E129F0", background: "#EB7DF4", highlight: {border: "#E129F0", background: "#F0B3F5"}}, // magenta + {border: "#7C29F0", background: "#AD85E4", highlight: {border: "#7C29F0", background: "#D3BDF0"}}, // purple + {border: "#C37F00", background: "#FFA807", highlight: {border: "#C37F00", background: "#FFCA66"}}, // orange + {border: "#4220FB", background: "#6E6EFD", highlight: {border: "#4220FB", background: "#9B9BFD"}}, // darkblue + {border: "#FD5A77", background: "#FFC0CB", highlight: {border: "#FD5A77", background: "#FFD1D9"}}, // pink + {border: "#4AD63A", background: "#C2FABC", highlight: {border: "#4AD63A", background: "#E6FFE3"}} // mint +]; + + +/** + * Clear all groups + */ +Groups.prototype.clear = function () { + this.groups = {}; + this.groups.length = function() + { + var i = 0; + for ( var p in this ) { + if (this.hasOwnProperty(p)) { + i++; + } + } + return i; + } +}; + + +/** + * get group properties of a groupname. If groupname is not found, a new group + * is added. + * @param {*} groupname Can be a number, string, Date, etc. + * @return {Object} group The created group, containing all group properties + */ +Groups.prototype.get = function (groupname) { + var group = this.groups[groupname]; + + if (group == undefined) { + // create new group + var index = this.defaultIndex % Groups.DEFAULT.length; + this.defaultIndex++; + group = {}; + group.color = Groups.DEFAULT[index]; + this.groups[groupname] = group; + } + + return group; +}; + +/** + * Add a custom group style + * @param {String} groupname + * @param {Object} style An object containing borderColor, + * backgroundColor, etc. + * @return {Object} group The created group object + */ +Groups.prototype.add = function (groupname, style) { + this.groups[groupname] = style; + if (style.color) { + style.color = Node.parseColor(style.color); + } + return style; +}; + +/** + * @class Images + * This class loads images and keeps them stored. + */ +Images = function () { + this.images = {}; + + this.callback = undefined; +}; + +/** + * Set an onload callback function. This will be called each time an image + * is loaded + * @param {function} callback + */ +Images.prototype.setOnloadCallback = function(callback) { + this.callback = callback; +}; + +/** + * + * @param {string} url Url of the image + * @return {Image} img The image object + */ +Images.prototype.load = function(url) { + var img = this.images[url]; + if (img == undefined) { + // create the image + var images = this; + img = new Image(); + this.images[url] = img; + img.onload = function() { + if (images.callback) { + images.callback(this); + } + }; + img.src = url; + } + + return img; +}; + +/** + * Created by Alex on 2/6/14. + */ + + +var physicsMixin = { + + /** + * Toggling barnes Hut calculation on and off. + * + * @private + */ + _toggleBarnesHut : function() { + this.constants.physics.barnesHut.enabled = !this.constants.physics.barnesHut.enabled; + this._loadSelectedForceSolver(); + this.moving = true; + this.start(); + }, + + + + /** + * This loads the node force solver based on the barnes hut or repulsion algorithm + * + * @private + */ + _loadSelectedForceSolver : function() { + // this overloads the this._calculateNodeForces + if (this.constants.physics.barnesHut.enabled == true) { + this._clearMixin(repulsionMixin); + this._clearMixin(hierarchalRepulsionMixin); + + this.constants.physics.centralGravity = this.constants.physics.barnesHut.centralGravity; + this.constants.physics.springLength = this.constants.physics.barnesHut.springLength; + this.constants.physics.springConstant = this.constants.physics.barnesHut.springConstant; + this.constants.physics.damping = this.constants.physics.barnesHut.damping; + + this._loadMixin(barnesHutMixin); + } + else if (this.constants.physics.hierarchicalRepulsion.enabled == true) { + this._clearMixin(barnesHutMixin); + this._clearMixin(repulsionMixin); + + this.constants.physics.centralGravity = this.constants.physics.hierarchicalRepulsion.centralGravity; + this.constants.physics.springLength = this.constants.physics.hierarchicalRepulsion.springLength; + this.constants.physics.springConstant = this.constants.physics.hierarchicalRepulsion.springConstant; + this.constants.physics.damping = this.constants.physics.hierarchicalRepulsion.damping; + + this._loadMixin(hierarchalRepulsionMixin); + } + else { + this._clearMixin(barnesHutMixin); + this._clearMixin(hierarchalRepulsionMixin); + this.barnesHutTree = undefined; + + this.constants.physics.centralGravity = this.constants.physics.repulsion.centralGravity; + this.constants.physics.springLength = this.constants.physics.repulsion.springLength; + this.constants.physics.springConstant = this.constants.physics.repulsion.springConstant; + this.constants.physics.damping = this.constants.physics.repulsion.damping; + + this._loadMixin(repulsionMixin); + } + }, + + /** + * Before calculating the forces, we check if we need to cluster to keep up performance and we check + * if there is more than one node. If it is just one node, we dont calculate anything. + * + * @private + */ + _initializeForceCalculation : function() { + // stop calculation if there is only one node + if (this.nodeIndices.length == 1) { + this.nodes[this.nodeIndices[0]]._setForce(0,0); + } + else { + // if there are too many nodes on screen, we cluster without repositioning + if (this.nodeIndices.length > this.constants.clustering.clusterThreshold && this.constants.clustering.enabled == true) { + this.clusterToFit(this.constants.clustering.reduceToNodes, false); + } + + // we now start the force calculation + this._calculateForces(); + } + }, + + + /** + * Calculate the external forces acting on the nodes + * Forces are caused by: edges, repulsing forces between nodes, gravity + * @private + */ + _calculateForces : function() { + // Gravity is required to keep separated groups from floating off + // the forces are reset to zero in this loop by using _setForce instead + // of _addForce + + this._calculateGravitationalForces(); + this._calculateNodeForces(); + + if (this.constants.smoothCurves == true) { + this._calculateSpringForcesWithSupport(); + } + else { + this._calculateSpringForces(); + } + }, + + + /** + * Smooth curves are created by adding invisible nodes in the center of the edges. These nodes are also + * handled in the calculateForces function. We then use a quadratic curve with the center node as control. + * This function joins the datanodes and invisible (called support) nodes into one object. + * We do this so we do not contaminate this.nodes with the support nodes. + * + * @private + */ + _updateCalculationNodes : function() { + if (this.constants.smoothCurves == true) { + this.calculationNodes = {}; + this.calculationNodeIndices = []; + + for (var nodeId in this.nodes) { + if (this.nodes.hasOwnProperty(nodeId)) { + this.calculationNodes[nodeId] = this.nodes[nodeId]; + } + } + var supportNodes = this.sectors['support']['nodes']; + for (var supportNodeId in supportNodes) { + if (supportNodes.hasOwnProperty(supportNodeId)) { + if (this.edges.hasOwnProperty(supportNodes[supportNodeId].parentEdgeId)) { + this.calculationNodes[supportNodeId] = supportNodes[supportNodeId]; + } + else { + supportNodes[supportNodeId]._setForce(0,0); + } + } + } + + for (var idx in this.calculationNodes) { + if (this.calculationNodes.hasOwnProperty(idx)) { + this.calculationNodeIndices.push(idx); + } + } + } + else { + this.calculationNodes = this.nodes; + this.calculationNodeIndices = this.nodeIndices; + } + }, + + + /** + * this function applies the central gravity effect to keep groups from floating off + * + * @private + */ + _calculateGravitationalForces : function() { + var dx, dy, distance, node, i; + var nodes = this.calculationNodes; + var gravity = this.constants.physics.centralGravity; + var gravityForce = 0; + + for (i = 0; i < this.calculationNodeIndices.length; i++) { + node = nodes[this.calculationNodeIndices[i]]; + node.damping = this.constants.physics.damping; // possibly add function to alter damping properties of clusters. + // gravity does not apply when we are in a pocket sector + if (this._sector() == "default" && gravity != 0) { + dx = -node.x; + dy = -node.y; + distance = Math.sqrt(dx*dx + dy*dy); + + gravityForce = (distance == 0) ? 0 : (gravity / distance); + node.fx = dx * gravityForce; + node.fy = dy * gravityForce; + } + else { + node.fx = 0; + node.fy = 0; + } + } + }, + + + /** + * this function calculates the effects of the springs in the case of unsmooth curves. + * + * @private + */ + _calculateSpringForces : function() { + var edgeLength, edge, edgeId; + var dx, dy, fx, fy, springForce, length; + var edges = this.edges; + + // forces caused by the edges, modelled as springs + for (edgeId in edges) { + if (edges.hasOwnProperty(edgeId)) { + edge = edges[edgeId]; + if (edge.connected) { + // only calculate forces if nodes are in the same sector + if (this.nodes.hasOwnProperty(edge.toId) && this.nodes.hasOwnProperty(edge.fromId)) { + edgeLength = edge.customLength ? edge.length : this.constants.physics.springLength; + // this implies that the edges between big clusters are longer + edgeLength += (edge.to.clusterSize + edge.from.clusterSize - 2) * this.constants.clustering.edgeGrowth; + + dx = (edge.from.x - edge.to.x); + dy = (edge.from.y - edge.to.y); + length = Math.sqrt(dx * dx + dy * dy); + + if (length == 0) { + length = 0.01; + } + + springForce = this.constants.physics.springConstant * (edgeLength - length) / length; + + fx = dx * springForce; + fy = dy * springForce; + + edge.from.fx += fx; + edge.from.fy += fy; + edge.to.fx -= fx; + edge.to.fy -= fy; + } + } + } + } + }, + + + /** + * This function calculates the springforces on the nodes, accounting for the support nodes. + * + * @private + */ + _calculateSpringForcesWithSupport : function() { + var edgeLength, edge, edgeId, combinedClusterSize; + var edges = this.edges; + + // forces caused by the edges, modelled as springs + for (edgeId in edges) { + if (edges.hasOwnProperty(edgeId)) { + edge = edges[edgeId]; + if (edge.connected) { + // only calculate forces if nodes are in the same sector + if (this.nodes.hasOwnProperty(edge.toId) && this.nodes.hasOwnProperty(edge.fromId)) { + if (edge.via != null) { + var node1 = edge.to; + var node2 = edge.via; + var node3 = edge.from; + + edgeLength = edge.customLength ? edge.length : this.constants.physics.springLength; + + combinedClusterSize = node1.clusterSize + node3.clusterSize - 2; + + // this implies that the edges between big clusters are longer + edgeLength += combinedClusterSize * this.constants.clustering.edgeGrowth; + this._calculateSpringForce(node1,node2,0.5*edgeLength); + this._calculateSpringForce(node2,node3,0.5*edgeLength); + } + } + } + } + } + }, + + + /** + * This is the code actually performing the calculation for the function above. It is split out to avoid repetition. + * + * @param node1 + * @param node2 + * @param edgeLength + * @private + */ + _calculateSpringForce : function(node1,node2,edgeLength) { + var dx, dy, fx, fy, springForce, length; + + dx = (node1.x - node2.x); + dy = (node1.y - node2.y); + length = Math.sqrt(dx * dx + dy * dy); + + if (length == 0) { + length = 0.01; + } + + springForce = this.constants.physics.springConstant * (edgeLength - length) / length; + + fx = dx * springForce; + fy = dy * springForce; + + node1.fx += fx; + node1.fy += fy; + node2.fx -= fx; + node2.fy -= fy; + }, + + + /** + * Load the HTML for the physics config and bind it + * @private + */ + _loadPhysicsConfiguration : function() { + if (this.physicsConfiguration === undefined) { + this.backupConstants = {}; + util.copyObject(this.constants,this.backupConstants); + + var hierarchicalLayoutDirections = ["LR","RL","UD","DU"]; + this.physicsConfiguration = document.createElement('div'); + this.physicsConfiguration.className = "PhysicsConfiguration"; + this.physicsConfiguration.innerHTML = '' + + '' + + '' + + '' + + ''+ + '' + + ''+ + '
Simulation Mode:
Barnes HutRepulsionHierarchical
' + + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + '' + + '' + + '' + + '' + + '' + + '' + + ''+ + '
Options:
' + this.containerElement.parentElement.insertBefore(this.physicsConfiguration,this.containerElement); + this.optionsDiv = document.createElement("div"); + this.optionsDiv.style.fontSize = "14px"; + this.optionsDiv.style.fontFamily = "verdana"; + this.containerElement.parentElement.insertBefore(this.optionsDiv,this.containerElement); + + var rangeElement; + rangeElement = document.getElementById('graph_BH_gc'); + rangeElement.onchange = showValueOfRange.bind(this,'graph_BH_gc',-1,"physics_barnesHut_gravitationalConstant"); + rangeElement = document.getElementById('graph_BH_cg'); + rangeElement.onchange = showValueOfRange.bind(this,'graph_BH_cg',1,"physics_centralGravity"); + rangeElement = document.getElementById('graph_BH_sc'); + rangeElement.onchange = showValueOfRange.bind(this,'graph_BH_sc',1,"physics_springConstant"); + rangeElement = document.getElementById('graph_BH_sl'); + rangeElement.onchange = showValueOfRange.bind(this,'graph_BH_sl',1,"physics_springLength"); + rangeElement = document.getElementById('graph_BH_damp'); + rangeElement.onchange = showValueOfRange.bind(this,'graph_BH_damp',1,"physics_damping"); + + rangeElement = document.getElementById('graph_R_nd'); + rangeElement.onchange = showValueOfRange.bind(this,'graph_R_nd',1,"physics_repulsion_nodeDistance"); + rangeElement = document.getElementById('graph_R_cg'); + rangeElement.onchange = showValueOfRange.bind(this,'graph_R_cg',1,"physics_centralGravity"); + rangeElement = document.getElementById('graph_R_sc'); + rangeElement.onchange = showValueOfRange.bind(this,'graph_R_sc',1,"physics_springConstant"); + rangeElement = document.getElementById('graph_R_sl'); + rangeElement.onchange = showValueOfRange.bind(this,'graph_R_sl',1,"physics_springLength"); + rangeElement = document.getElementById('graph_R_damp'); + rangeElement.onchange = showValueOfRange.bind(this,'graph_R_damp',1,"physics_damping"); + + rangeElement = document.getElementById('graph_H_nd'); + rangeElement.onchange = showValueOfRange.bind(this,'graph_H_nd',1,"physics_hierarchicalRepulsion_nodeDistance"); + rangeElement = document.getElementById('graph_H_cg'); + rangeElement.onchange = showValueOfRange.bind(this,'graph_H_cg',1,"physics_centralGravity"); + rangeElement = document.getElementById('graph_H_sc'); + rangeElement.onchange = showValueOfRange.bind(this,'graph_H_sc',1,"physics_springConstant"); + rangeElement = document.getElementById('graph_H_sl'); + rangeElement.onchange = showValueOfRange.bind(this,'graph_H_sl',1,"physics_springLength"); + rangeElement = document.getElementById('graph_H_damp'); + rangeElement.onchange = showValueOfRange.bind(this,'graph_H_damp',1,"physics_damping"); + rangeElement = document.getElementById('graph_H_direction'); + rangeElement.onchange = showValueOfRange.bind(this,'graph_H_direction',hierarchicalLayoutDirections,"hierarchicalLayout_direction"); + rangeElement = document.getElementById('graph_H_levsep'); + rangeElement.onchange = showValueOfRange.bind(this,'graph_H_levsep',1,"hierarchicalLayout_levelSeparation"); + rangeElement = document.getElementById('graph_H_nspac'); + rangeElement.onchange = showValueOfRange.bind(this,'graph_H_nspac',1,"hierarchicalLayout_nodeSpacing"); + + var radioButton1 = document.getElementById("graph_physicsMethod1"); + var radioButton2 = document.getElementById("graph_physicsMethod2"); + var radioButton3 = document.getElementById("graph_physicsMethod3"); + radioButton2.checked = true; + if (this.constants.physics.barnesHut.enabled) { + radioButton1.checked = true; + } + if (this.constants.hierarchicalLayout.enabled) { + radioButton3.checked = true; + } + + var graph_toggleSmooth = document.getElementById("graph_toggleSmooth"); + var graph_repositionNodes = document.getElementById("graph_repositionNodes"); + var graph_generateOptions = document.getElementById("graph_generateOptions"); + + graph_toggleSmooth.onclick = graphToggleSmoothCurves.bind(this); + graph_repositionNodes.onclick = graphRepositionNodes.bind(this); + graph_generateOptions.onclick = graphGenerateOptions.bind(this); + if (this.constants.smoothCurves == true) {graph_toggleSmooth.style.background = "#A4FF56";} + else {graph_toggleSmooth.style.background = "#FF8532";} + + + switchConfigurations.apply(this); + + radioButton1.onchange = switchConfigurations.bind(this); + radioButton2.onchange = switchConfigurations.bind(this); + radioButton3.onchange = switchConfigurations.bind(this); + } + }, + + _overWriteGraphConstants : function(constantsVariableName, value) { + var nameArray = constantsVariableName.split("_"); + if (nameArray.length == 1) { + this.constants[nameArray[0]] = value; + } + else if (nameArray.length == 2) { + this.constants[nameArray[0]][nameArray[1]] = value; + } + else if (nameArray.length == 3) { + this.constants[nameArray[0]][nameArray[1]][nameArray[2]] = value; + } + } +} + +function graphToggleSmoothCurves () { + this.constants.smoothCurves = !this.constants.smoothCurves; + var graph_toggleSmooth = document.getElementById("graph_toggleSmooth"); + if (this.constants.smoothCurves == true) {graph_toggleSmooth.style.background = "#A4FF56";} + else {graph_toggleSmooth.style.background = "#FF8532";} + + this._configureSmoothCurves(false); +}; + +function graphRepositionNodes () { + for (var nodeId in this.calculationNodes) { + if (this.calculationNodes.hasOwnProperty(nodeId)) { + this.calculationNodes[nodeId].vx = 0; this.calculationNodes[nodeId].vy = 0; + this.calculationNodes[nodeId].fx = 0; this.calculationNodes[nodeId].fy = 0; + } + } + if (this.constants.hierarchicalLayout.enabled == true) { + this._setupHierarchicalLayout(); + } + else { + this.repositionNodes(); + } + this.moving = true; + this.start(); +}; + +function graphGenerateOptions () { + var options = "No options are required, default values used." + var optionsSpecific = []; + var radioButton1 = document.getElementById("graph_physicsMethod1"); + var radioButton2 = document.getElementById("graph_physicsMethod2"); + if (radioButton1.checked == true) { + if (this.constants.physics.barnesHut.gravitationalConstant != this.backupConstants.physics.barnesHut.gravitationalConstant) {optionsSpecific.push("gravitationalConstant: " + this.constants.physics.barnesHut.gravitationalConstant);} + if (this.constants.physics.centralGravity != this.backupConstants.physics.barnesHut.centralGravity) {optionsSpecific.push("centralGravity: " + this.constants.physics.centralGravity);} + if (this.constants.physics.springLength != this.backupConstants.physics.barnesHut.springLength) {optionsSpecific.push("springLength: " + this.constants.physics.springLength);} + if (this.constants.physics.springConstant != this.backupConstants.physics.barnesHut.springConstant) {optionsSpecific.push("springConstant: " + this.constants.physics.springConstant);} + if (this.constants.physics.damping != this.backupConstants.physics.barnesHut.damping) {optionsSpecific.push("damping: " + this.constants.physics.damping);} + if (optionsSpecific.length != 0) { + options = "var options = {" + options += "physics: {barnesHut: {" + for (var i = 0; i < optionsSpecific.length; i++) { + options += optionsSpecific[i]; + if (i < optionsSpecific.length - 1) { + options += ", " + } + } + options += '}}' + } + if (this.constants.smoothCurves != this.backupConstants.smoothCurves) { + if (optionsSpecific.length == 0) {options = "var options = {";} + else {options += ", "} + options += "smoothCurves: " + this.constants.smoothCurves; + } + if (options != "No options are required, default values used.") { + options += '};' + } + } + else if (radioButton2.checked == true) { + options = "var options = {" + options += "physics: {barnesHut: {enabled: false}"; + if (this.constants.physics.repulsion.nodeDistance != this.backupConstants.physics.repulsion.nodeDistance) {optionsSpecific.push("nodeDistance: " + this.constants.physics.repulsion.nodeDistance);} + if (this.constants.physics.centralGravity != this.backupConstants.physics.repulsion.centralGravity) {optionsSpecific.push("centralGravity: " + this.constants.physics.centralGravity);} + if (this.constants.physics.springLength != this.backupConstants.physics.repulsion.springLength) {optionsSpecific.push("springLength: " + this.constants.physics.springLength);} + if (this.constants.physics.springConstant != this.backupConstants.physics.repulsion.springConstant) {optionsSpecific.push("springConstant: " + this.constants.physics.springConstant);} + if (this.constants.physics.damping != this.backupConstants.physics.repulsion.damping) {optionsSpecific.push("damping: " + this.constants.physics.damping);} + if (optionsSpecific.length != 0) { + options += ", repulsion: {" + for (var i = 0; i < optionsSpecific.length; i++) { + options += optionsSpecific[i]; + if (i < optionsSpecific.length - 1) { + options += ", " + } + } + options += '}}' + } + if (optionsSpecific.length == 0) {options += "}"} + if (this.constants.smoothCurves != this.backupConstants.smoothCurves) { + options += ", smoothCurves: " + this.constants.smoothCurves; + } + options += '};' + } + else { + options = "var options = {" + if (this.constants.physics.hierarchicalRepulsion.nodeDistance != this.backupConstants.physics.hierarchicalRepulsion.nodeDistance) {optionsSpecific.push("nodeDistance: " + this.constants.physics.hierarchicalRepulsion.nodeDistance);} + if (this.constants.physics.centralGravity != this.backupConstants.physics.hierarchicalRepulsion.centralGravity) {optionsSpecific.push("centralGravity: " + this.constants.physics.centralGravity);} + if (this.constants.physics.springLength != this.backupConstants.physics.hierarchicalRepulsion.springLength) {optionsSpecific.push("springLength: " + this.constants.physics.springLength);} + if (this.constants.physics.springConstant != this.backupConstants.physics.hierarchicalRepulsion.springConstant) {optionsSpecific.push("springConstant: " + this.constants.physics.springConstant);} + if (this.constants.physics.damping != this.backupConstants.physics.hierarchicalRepulsion.damping) {optionsSpecific.push("damping: " + this.constants.physics.damping);} + if (optionsSpecific.length != 0) { + options += "physics: {hierarchicalRepulsion: {" + for (var i = 0; i < optionsSpecific.length; i++) { + options += optionsSpecific[i]; + if (i < optionsSpecific.length - 1) { + options += ", "; + } + } + options += '}},'; + } + options += 'hierarchicalLayout: {'; + optionsSpecific = []; + if (this.constants.hierarchicalLayout.direction != this.backupConstants.hierarchicalLayout.direction) {optionsSpecific.push("direction: " + this.constants.hierarchicalLayout.direction);} + if (Math.abs(this.constants.hierarchicalLayout.levelSeparation) != this.backupConstants.hierarchicalLayout.levelSeparation) {optionsSpecific.push("levelSeparation: " + this.constants.hierarchicalLayout.levelSeparation);} + if (this.constants.hierarchicalLayout.nodeSpacing != this.backupConstants.hierarchicalLayout.nodeSpacing) {optionsSpecific.push("nodeSpacing: " + this.constants.hierarchicalLayout.nodeSpacing);} + if (optionsSpecific.length != 0) { + for (var i = 0; i < optionsSpecific.length; i++) { + options += optionsSpecific[i]; + if (i < optionsSpecific.length - 1) { + options += ", " + } + } + options += '}' + } + else { + options += "enabled:true}"; + } + options += '};' + } + + + this.optionsDiv.innerHTML = options; + +}; + + +function switchConfigurations () { + var ids = ["graph_BH_table","graph_R_table","graph_H_table"] + var radioButton = document.querySelector('input[name="graph_physicsMethod"]:checked').value; + var tableId = "graph_" + radioButton + "_table"; + var table = document.getElementById(tableId); + table.style.display = "block"; + for (var i = 0; i < ids.length; i++) { + if (ids[i] != tableId) { + table = document.getElementById(ids[i]); + table.style.display = "none"; + } + } + this._restoreNodes(); + if (radioButton == "R") { + this.constants.hierarchicalLayout.enabled = false; + this.constants.physics.hierarchicalRepulsion.enabled = false; + this.constants.physics.barnesHut.enabled = false; + } + else if (radioButton == "H") { + this.constants.hierarchicalLayout.enabled = true; + this.constants.physics.hierarchicalRepulsion.enabled = true; + this.constants.physics.barnesHut.enabled = false; + this._setupHierarchicalLayout(); + } + else { + this.constants.hierarchicalLayout.enabled = false; + this.constants.physics.hierarchicalRepulsion.enabled = false; + this.constants.physics.barnesHut.enabled = true; + } + this._loadSelectedForceSolver(); + var graph_toggleSmooth = document.getElementById("graph_toggleSmooth"); + if (this.constants.smoothCurves == true) {graph_toggleSmooth.style.background = "#A4FF56";} + else {graph_toggleSmooth.style.background = "#FF8532";} + this.moving = true; + this.start(); + +} + +function showValueOfRange (id,map,constantsVariableName) { + var valueId = id + "_value"; + var rangeValue = document.getElementById(id).value; + + if (map instanceof Array) { + document.getElementById(valueId).value = map[parseInt(rangeValue)]; + this._overWriteGraphConstants(constantsVariableName,map[parseInt(rangeValue)]); + } + else { + document.getElementById(valueId).value = parseInt(map) * parseFloat(rangeValue); + this._overWriteGraphConstants(constantsVariableName, parseInt(map) * parseFloat(rangeValue)); + } + + if (constantsVariableName == "hierarchicalLayout_direction" || + constantsVariableName == "hierarchicalLayout_levelSeparation" || + constantsVariableName == "hierarchicalLayout_nodeSpacing") { + this._setupHierarchicalLayout(); + } + this.moving = true; + this.start(); +}; + + + +/** + * Created by Alex on 2/10/14. + */ + +var hierarchalRepulsionMixin = { + + + /** + * Calculate the forces the nodes apply on eachother based on a repulsion field. + * This field is linearly approximated. + * + * @private + */ + _calculateNodeForces : function() { + var dx, dy, distance, fx, fy, combinedClusterSize, + repulsingForce, node1, node2, i, j; + + var nodes = this.calculationNodes; + var nodeIndices = this.calculationNodeIndices; + + // approximation constants + var b = 5; + var a_base = 0.5*-b; + + + // repulsing forces between nodes + var nodeDistance = this.constants.physics.hierarchicalRepulsion.nodeDistance; + var minimumDistance = nodeDistance; + + // we loop from i over all but the last entree in the array + // j loops from i+1 to the last. This way we do not double count any of the indices, nor i == j + for (i = 0; i < nodeIndices.length-1; i++) { + + node1 = nodes[nodeIndices[i]]; + for (j = i+1; j < nodeIndices.length; j++) { + node2 = nodes[nodeIndices[j]]; + + dx = node2.x - node1.x; + dy = node2.y - node1.y; + distance = Math.sqrt(dx * dx + dy * dy); + + var a = a_base / minimumDistance; + if (distance < 2*minimumDistance) { + repulsingForce = a * distance + b; // linear approx of 1 / (1 + Math.exp((distance / minimumDistance - 1) * steepness)) + + // normalize force with + if (distance == 0) { + distance = 0.01; + } + else { + repulsingForce = repulsingForce/distance; + } + fx = dx * repulsingForce; + fy = dy * repulsingForce; + + node1.fx -= fx; + node1.fy -= fy; + node2.fx += fx; + node2.fy += fy; + } + } + } + } +} +/** + * Created by Alex on 2/10/14. + */ + +var barnesHutMixin = { + + /** + * This function calculates the forces the nodes apply on eachother based on a gravitational model. + * The Barnes Hut method is used to speed up this N-body simulation. + * + * @private + */ + _calculateNodeForces : function() { + var node; + var nodes = this.calculationNodes; + var nodeIndices = this.calculationNodeIndices; + var nodeCount = nodeIndices.length; + + this._formBarnesHutTree(nodes,nodeIndices); + + var barnesHutTree = this.barnesHutTree; + + // place the nodes one by one recursively + for (var i = 0; i < nodeCount; i++) { + node = nodes[nodeIndices[i]]; + // starting with root is irrelevant, it never passes the BarnesHut condition + this._getForceContribution(barnesHutTree.root.children.NW,node); + this._getForceContribution(barnesHutTree.root.children.NE,node); + this._getForceContribution(barnesHutTree.root.children.SW,node); + this._getForceContribution(barnesHutTree.root.children.SE,node); + } + }, + + + /** + * This function traverses the barnesHutTree. It checks when it can approximate distant nodes with their center of mass. + * If a region contains a single node, we check if it is not itself, then we apply the force. + * + * @param parentBranch + * @param node + * @private + */ + _getForceContribution : function(parentBranch,node) { + // we get no force contribution from an empty region + if (parentBranch.childrenCount > 0) { + var dx,dy,distance; + + // get the distance from the center of mass to the node. + dx = parentBranch.centerOfMass.x - node.x; + dy = parentBranch.centerOfMass.y - node.y; + distance = Math.sqrt(dx * dx + dy * dy); + + // BarnesHut condition + // original condition : s/d < theta = passed === d/s > 1/theta = passed + // calcSize = 1/s --> d * 1/s > 1/theta = passed + if (distance * parentBranch.calcSize > this.constants.physics.barnesHut.theta) { + // duplicate code to reduce function calls to speed up program + if (distance == 0) { + distance = 0.1*Math.random(); + dx = distance; + } + var gravityForce = this.constants.physics.barnesHut.gravitationalConstant * parentBranch.mass * node.mass / (distance * distance * distance); + var fx = dx * gravityForce; + var fy = dy * gravityForce; + node.fx += fx; + node.fy += fy; + } + else { + // Did not pass the condition, go into children if available + if (parentBranch.childrenCount == 4) { + this._getForceContribution(parentBranch.children.NW,node); + this._getForceContribution(parentBranch.children.NE,node); + this._getForceContribution(parentBranch.children.SW,node); + this._getForceContribution(parentBranch.children.SE,node); + } + else { // parentBranch must have only one node, if it was empty we wouldnt be here + if (parentBranch.children.data.id != node.id) { // if it is not self + // duplicate code to reduce function calls to speed up program + if (distance == 0) { + distance = 0.5*Math.random(); + dx = distance; + } + var gravityForce = this.constants.physics.barnesHut.gravitationalConstant * parentBranch.mass * node.mass / (distance * distance * distance); + var fx = dx * gravityForce; + var fy = dy * gravityForce; + node.fx += fx; + node.fy += fy; + } + } + } + } + }, + + /** + * This function constructs the barnesHut tree recursively. It creates the root, splits it and starts placing the nodes. + * + * @param nodes + * @param nodeIndices + * @private + */ + _formBarnesHutTree : function(nodes,nodeIndices) { + var node; + var nodeCount = nodeIndices.length; + + var minX = Number.MAX_VALUE, + minY = Number.MAX_VALUE, + maxX =-Number.MAX_VALUE, + maxY =-Number.MAX_VALUE; + + // get the range of the nodes + for (var i = 0; i < nodeCount; i++) { + var x = nodes[nodeIndices[i]].x; + var y = nodes[nodeIndices[i]].y; + if (x < minX) { minX = x; } + if (x > maxX) { maxX = x; } + if (y < minY) { minY = y; } + if (y > maxY) { maxY = y; } + } + // make the range a square + var sizeDiff = Math.abs(maxX - minX) - Math.abs(maxY - minY); // difference between X and Y + if (sizeDiff > 0) {minY -= 0.5 * sizeDiff; maxY += 0.5 * sizeDiff;} // xSize > ySize + else {minX += 0.5 * sizeDiff; maxX -= 0.5 * sizeDiff;} // xSize < ySize + + + var minimumTreeSize = 1e-5; + var rootSize = Math.max(minimumTreeSize,Math.abs(maxX - minX)); + var halfRootSize = 0.5 * rootSize; + var centerX = 0.5 * (minX + maxX), centerY = 0.5 * (minY + maxY); + + // construct the barnesHutTree + var barnesHutTree = {root:{ + centerOfMass:{x:0,y:0}, // Center of Mass + mass:0, + range: {minX:centerX-halfRootSize,maxX:centerX+halfRootSize, + minY:centerY-halfRootSize,maxY:centerY+halfRootSize}, + + size: rootSize, + calcSize: 1 / rootSize, + children: {data:null}, + maxWidth: 0, + level: 0, + childrenCount: 4 + }}; + this._splitBranch(barnesHutTree.root); + + // place the nodes one by one recursively + for (i = 0; i < nodeCount; i++) { + node = nodes[nodeIndices[i]]; + this._placeInTree(barnesHutTree.root,node); + } + + // make global + this.barnesHutTree = barnesHutTree + }, + + + _updateBranchMass : function(parentBranch, node) { + var totalMass = parentBranch.mass + node.mass; + var totalMassInv = 1/totalMass; + + parentBranch.centerOfMass.x = parentBranch.centerOfMass.x * parentBranch.mass + node.x * node.mass; + parentBranch.centerOfMass.x *= totalMassInv; + + parentBranch.centerOfMass.y = parentBranch.centerOfMass.y * parentBranch.mass + node.y * node.mass; + parentBranch.centerOfMass.y *= totalMassInv; + + parentBranch.mass = totalMass; + var biggestSize = Math.max(Math.max(node.height,node.radius),node.width); + parentBranch.maxWidth = (parentBranch.maxWidth < biggestSize) ? biggestSize : parentBranch.maxWidth; + + }, + + + _placeInTree : function(parentBranch,node,skipMassUpdate) { + if (skipMassUpdate != true || skipMassUpdate === undefined) { + // update the mass of the branch. + this._updateBranchMass(parentBranch,node); + } + + if (parentBranch.children.NW.range.maxX > node.x) { // in NW or SW + if (parentBranch.children.NW.range.maxY > node.y) { // in NW + this._placeInRegion(parentBranch,node,"NW"); + } + else { // in SW + this._placeInRegion(parentBranch,node,"SW"); + } + } + else { // in NE or SE + if (parentBranch.children.NW.range.maxY > node.y) { // in NE + this._placeInRegion(parentBranch,node,"NE"); + } + else { // in SE + this._placeInRegion(parentBranch,node,"SE"); + } + } + }, + + + _placeInRegion : function(parentBranch,node,region) { + switch (parentBranch.children[region].childrenCount) { + case 0: // place node here + parentBranch.children[region].children.data = node; + parentBranch.children[region].childrenCount = 1; + this._updateBranchMass(parentBranch.children[region],node); + break; + case 1: // convert into children + // if there are two nodes exactly overlapping (on init, on opening of cluster etc.) + // we move one node a pixel and we do not put it in the tree. + if (parentBranch.children[region].children.data.x == node.x && + parentBranch.children[region].children.data.y == node.y) { + node.x += Math.random(); + node.y += Math.random(); + } + else { + this._splitBranch(parentBranch.children[region]); + this._placeInTree(parentBranch.children[region],node); + } + break; + case 4: // place in branch + this._placeInTree(parentBranch.children[region],node); + break; + } + }, + + + /** + * this function splits a branch into 4 sub branches. If the branch contained a node, we place it in the subbranch + * after the split is complete. + * + * @param parentBranch + * @private + */ + _splitBranch : function(parentBranch) { + // if the branch is filled with a node, replace the node in the new subset. + var containedNode = null; + if (parentBranch.childrenCount == 1) { + containedNode = parentBranch.children.data; + parentBranch.mass = 0; parentBranch.centerOfMass.x = 0; parentBranch.centerOfMass.y = 0; + } + parentBranch.childrenCount = 4; + parentBranch.children.data = null; + this._insertRegion(parentBranch,"NW"); + this._insertRegion(parentBranch,"NE"); + this._insertRegion(parentBranch,"SW"); + this._insertRegion(parentBranch,"SE"); + + if (containedNode != null) { + this._placeInTree(parentBranch,containedNode); + } + }, + + + /** + * This function subdivides the region into four new segments. + * Specifically, this inserts a single new segment. + * It fills the children section of the parentBranch + * + * @param parentBranch + * @param region + * @param parentRange + * @private + */ + _insertRegion : function(parentBranch, region) { + var minX,maxX,minY,maxY; + var childSize = 0.5 * parentBranch.size; + switch (region) { + case "NW": + minX = parentBranch.range.minX; + maxX = parentBranch.range.minX + childSize; + minY = parentBranch.range.minY; + maxY = parentBranch.range.minY + childSize; + break; + case "NE": + minX = parentBranch.range.minX + childSize; + maxX = parentBranch.range.maxX; + minY = parentBranch.range.minY; + maxY = parentBranch.range.minY + childSize; + break; + case "SW": + minX = parentBranch.range.minX; + maxX = parentBranch.range.minX + childSize; + minY = parentBranch.range.minY + childSize; + maxY = parentBranch.range.maxY; + break; + case "SE": + minX = parentBranch.range.minX + childSize; + maxX = parentBranch.range.maxX; + minY = parentBranch.range.minY + childSize; + maxY = parentBranch.range.maxY; + break; + } + + + parentBranch.children[region] = { + centerOfMass:{x:0,y:0}, + mass:0, + range:{minX:minX,maxX:maxX,minY:minY,maxY:maxY}, + size: 0.5 * parentBranch.size, + calcSize: 2 * parentBranch.calcSize, + children: {data:null}, + maxWidth: 0, + level: parentBranch.level+1, + childrenCount: 0 + }; + }, + + + /** + * This function is for debugging purposed, it draws the tree. + * + * @param ctx + * @param color + * @private + */ + _drawTree : function(ctx,color) { + if (this.barnesHutTree !== undefined) { + + ctx.lineWidth = 1; + + this._drawBranch(this.barnesHutTree.root,ctx,color); + } + }, + + + /** + * This function is for debugging purposes. It draws the branches recursively. + * + * @param branch + * @param ctx + * @param color + * @private + */ + _drawBranch : function(branch,ctx,color) { + if (color === undefined) { + color = "#FF0000"; + } + + if (branch.childrenCount == 4) { + this._drawBranch(branch.children.NW,ctx); + this._drawBranch(branch.children.NE,ctx); + this._drawBranch(branch.children.SE,ctx); + this._drawBranch(branch.children.SW,ctx); + } + ctx.strokeStyle = color; + ctx.beginPath(); + ctx.moveTo(branch.range.minX,branch.range.minY); + ctx.lineTo(branch.range.maxX,branch.range.minY); + ctx.stroke(); + + ctx.beginPath(); + ctx.moveTo(branch.range.maxX,branch.range.minY); + ctx.lineTo(branch.range.maxX,branch.range.maxY); + ctx.stroke(); + + ctx.beginPath(); + ctx.moveTo(branch.range.maxX,branch.range.maxY); + ctx.lineTo(branch.range.minX,branch.range.maxY); + ctx.stroke(); + + ctx.beginPath(); + ctx.moveTo(branch.range.minX,branch.range.maxY); + ctx.lineTo(branch.range.minX,branch.range.minY); + ctx.stroke(); + + /* + if (branch.mass > 0) { + ctx.circle(branch.centerOfMass.x, branch.centerOfMass.y, 3*branch.mass); + ctx.stroke(); + } + */ + } + +}; +/** + * Created by Alex on 2/10/14. + */ + +var repulsionMixin = { + + + /** + * Calculate the forces the nodes apply on eachother based on a repulsion field. + * This field is linearly approximated. + * + * @private + */ + _calculateNodeForces : function() { + var dx, dy, angle, distance, fx, fy, combinedClusterSize, + repulsingForce, node1, node2, i, j; + + var nodes = this.calculationNodes; + var nodeIndices = this.calculationNodeIndices; + + // approximation constants + var a_base = -2/3; + var b = 4/3; + + // repulsing forces between nodes + var nodeDistance = this.constants.physics.repulsion.nodeDistance; + var minimumDistance = nodeDistance; + + // we loop from i over all but the last entree in the array + // j loops from i+1 to the last. This way we do not double count any of the indices, nor i == j + for (i = 0; i < nodeIndices.length-1; i++) { + node1 = nodes[nodeIndices[i]]; + for (j = i+1; j < nodeIndices.length; j++) { + node2 = nodes[nodeIndices[j]]; + combinedClusterSize = node1.clusterSize + node2.clusterSize - 2; + + dx = node2.x - node1.x; + dy = node2.y - node1.y; + distance = Math.sqrt(dx * dx + dy * dy); + + minimumDistance = (combinedClusterSize == 0) ? nodeDistance : (nodeDistance * (1 + combinedClusterSize * this.constants.clustering.distanceAmplification)); + var a = a_base / minimumDistance; + if (distance < 2*minimumDistance) { + if (distance < 0.5*minimumDistance) { + repulsingForce = 1.0; + } + else { + repulsingForce = a * distance + b; // linear approx of 1 / (1 + Math.exp((distance / minimumDistance - 1) * steepness)) + } + + // amplify the repulsion for clusters. + repulsingForce *= (combinedClusterSize == 0) ? 1 : 1 + combinedClusterSize * this.constants.clustering.forceAmplification; + repulsingForce = repulsingForce/distance; + + fx = dx * repulsingForce; + fy = dy * repulsingForce; + + node1.fx -= fx; + node1.fy -= fy; + node2.fx += fx; + node2.fy += fy; + } + } + } + } +} +var HierarchicalLayoutMixin = { + + + /** + * This is the main function to layout the nodes in a hierarchical way. + * It checks if the node details are supplied correctly + * + * @private + */ + _setupHierarchicalLayout : function() { + if (this.constants.hierarchicalLayout.enabled == true) { + if (this.constants.hierarchicalLayout.direction == "RL" || this.constants.hierarchicalLayout.direction == "DU") { + this.constants.hierarchicalLayout.levelSeparation *= -1; + } + else { + this.constants.hierarchicalLayout.levelSeparation = Math.abs(this.constants.hierarchicalLayout.levelSeparation); + } + // get the size of the largest hubs and check if the user has defined a level for a node. + var hubsize = 0; + var node, nodeId; + var definedLevel = false; + var undefinedLevel = false; + + for (nodeId in this.nodes) { + if (this.nodes.hasOwnProperty(nodeId)) { + node = this.nodes[nodeId]; + if (node.level != -1) { + definedLevel = true; + } + else { + undefinedLevel = true; + } + if (hubsize < node.edges.length) { + hubsize = node.edges.length; + } + } + } + + // if the user defined some levels but not all, alert and run without hierarchical layout + if (undefinedLevel == true && definedLevel == true) { + alert("To use the hierarchical layout, nodes require either no predefined levels or levels have to be defined for all nodes.") + this.zoomExtent(true,this.constants.clustering.enabled); + if (!this.constants.clustering.enabled) { + this.start(); + } + } + else { + // setup the system to use hierarchical method. + this._changeConstants(); + + // define levels if undefined by the users. Based on hubsize + if (undefinedLevel == true) { + this._determineLevels(hubsize); + } + // check the distribution of the nodes per level. + var distribution = this._getDistribution(); + + // place the nodes on the canvas. This also stablilizes the system. + this._placeNodesByHierarchy(distribution); + + // start the simulation. + this.start(); + } + } + }, + + + /** + * This function places the nodes on the canvas based on the hierarchial distribution. + * + * @param {Object} distribution | obtained by the function this._getDistribution() + * @private + */ + _placeNodesByHierarchy : function(distribution) { + var nodeId, node; + + // start placing all the level 0 nodes first. Then recursively position their branches. + for (nodeId in distribution[0].nodes) { + if (distribution[0].nodes.hasOwnProperty(nodeId)) { + node = distribution[0].nodes[nodeId]; + if (this.constants.hierarchicalLayout.direction == "UD" || this.constants.hierarchicalLayout.direction == "DU") { + if (node.xFixed) { + node.x = distribution[0].minPos; + node.xFixed = false; + + distribution[0].minPos += distribution[0].nodeSpacing; + } + } + else { + if (node.yFixed) { + node.y = distribution[0].minPos; + node.yFixed = false; + + distribution[0].minPos += distribution[0].nodeSpacing; + } + } + this._placeBranchNodes(node.edges,node.id,distribution,node.level); + } + } + + // stabilize the system after positioning. This function calls zoomExtent. + this._stabilize(); + }, + + + /** + * This function get the distribution of levels based on hubsize + * + * @returns {Object} + * @private + */ + _getDistribution : function() { + var distribution = {}; + var nodeId, node; + + // we fix Y because the hierarchy is vertical, we fix X so we do not give a node an x position for a second time. + // the fix of X is removed after the x value has been set. + for (nodeId in this.nodes) { + if (this.nodes.hasOwnProperty(nodeId)) { + node = this.nodes[nodeId]; + node.xFixed = true; + node.yFixed = true; + if (this.constants.hierarchicalLayout.direction == "UD" || this.constants.hierarchicalLayout.direction == "DU") { + node.y = this.constants.hierarchicalLayout.levelSeparation*node.level; + } + else { + node.x = this.constants.hierarchicalLayout.levelSeparation*node.level; + } + if (!distribution.hasOwnProperty(node.level)) { + distribution[node.level] = {amount: 0, nodes: {}, minPos:0, nodeSpacing:0}; + } + distribution[node.level].amount += 1; + distribution[node.level].nodes[node.id] = node; + } + } + + // determine the largest amount of nodes of all levels + var maxCount = 0; + for (var level in distribution) { + if (distribution.hasOwnProperty(level)) { + if (maxCount < distribution[level].amount) { + maxCount = distribution[level].amount; + } + } + } + + // set the initial position and spacing of each nodes accordingly + for (var level in distribution) { + if (distribution.hasOwnProperty(level)) { + distribution[level].nodeSpacing = (maxCount + 1) * this.constants.hierarchicalLayout.nodeSpacing; + distribution[level].nodeSpacing /= (distribution[level].amount + 1); + distribution[level].minPos = distribution[level].nodeSpacing - (0.5 * (distribution[level].amount + 1) * distribution[level].nodeSpacing); + } + } + + return distribution; + }, + + + /** + * this function allocates nodes in levels based on the recursive branching from the largest hubs. + * + * @param hubsize + * @private + */ + _determineLevels : function(hubsize) { + var nodeId, node; + + // determine hubs + for (nodeId in this.nodes) { + if (this.nodes.hasOwnProperty(nodeId)) { + node = this.nodes[nodeId]; + if (node.edges.length == hubsize) { + node.level = 0; + } + } + } + + // branch from hubs + for (nodeId in this.nodes) { + if (this.nodes.hasOwnProperty(nodeId)) { + node = this.nodes[nodeId]; + if (node.level == 0) { + this._setLevel(1,node.edges,node.id); + } + } + } + }, + + + /** + * Since hierarchical layout does not support: + * - smooth curves (based on the physics), + * - clustering (based on dynamic node counts) + * + * We disable both features so there will be no problems. + * + * @private + */ + _changeConstants : function() { + this.constants.clustering.enabled = false; + this.constants.physics.barnesHut.enabled = false; + this.constants.physics.hierarchicalRepulsion.enabled = true; + this._loadSelectedForceSolver(); + this.constants.smoothCurves = false; + this._configureSmoothCurves(); + }, + + + /** + * This is a recursively called function to enumerate the branches from the largest hubs and place the nodes + * on a X position that ensures there will be no overlap. + * + * @param edges + * @param parentId + * @param distribution + * @param parentLevel + * @private + */ + _placeBranchNodes : function(edges, parentId, distribution, parentLevel) { + for (var i = 0; i < edges.length; i++) { + var childNode = null; + if (edges[i].toId == parentId) { + childNode = edges[i].from; + } + else { + childNode = edges[i].to; + } + + // if a node is conneceted to another node on the same level (or higher (means lower level))!, this is not handled here. + var nodeMoved = false; + if (this.constants.hierarchicalLayout.direction == "UD" || this.constants.hierarchicalLayout.direction == "DU") { + if (childNode.xFixed && childNode.level > parentLevel) { + childNode.xFixed = false; + childNode.x = distribution[childNode.level].minPos; + nodeMoved = true; + } + } + else { + if (childNode.yFixed && childNode.level > parentLevel) { + childNode.yFixed = false; + childNode.y = distribution[childNode.level].minPos; + nodeMoved = true; + } + } + + if (nodeMoved == true) { + distribution[childNode.level].minPos += distribution[childNode.level].nodeSpacing; + if (childNode.edges.length > 1) { + this._placeBranchNodes(childNode.edges,childNode.id,distribution,childNode.level); + } + } + } + }, + + + /** + * this function is called recursively to enumerate the barnches of the largest hubs and give each node a level. + * + * @param level + * @param edges + * @param parentId + * @private + */ + _setLevel : function(level, edges, parentId) { + for (var i = 0; i < edges.length; i++) { + var childNode = null; + if (edges[i].toId == parentId) { + childNode = edges[i].from; + } + else { + childNode = edges[i].to; + } + if (childNode.level == -1 || childNode.level > level) { + childNode.level = level; + if (edges.length > 1) { + this._setLevel(level+1, childNode.edges, childNode.id); + } + } + } + }, + + + /** + * Unfix nodes + * + * @private + */ + _restoreNodes : function() { + for (nodeId in this.nodes) { + if (this.nodes.hasOwnProperty(nodeId)) { + this.nodes[nodeId].xFixed = false; + this.nodes[nodeId].yFixed = false; + } + } + } + + +}; +/** + * Created by Alex on 2/4/14. + */ + +var manipulationMixin = { + + /** + * clears the toolbar div element of children + * + * @private + */ + _clearManipulatorBar : function() { + while (this.manipulationDiv.hasChildNodes()) { + this.manipulationDiv.removeChild(this.manipulationDiv.firstChild); + } + }, + + /** + * Manipulation UI temporarily overloads certain functions to extend or replace them. To be able to restore + * these functions to their original functionality, we saved them in this.cachedFunctions. + * This function restores these functions to their original function. + * + * @private + */ + _restoreOverloadedFunctions : function() { + for (var functionName in this.cachedFunctions) { + if (this.cachedFunctions.hasOwnProperty(functionName)) { + this[functionName] = this.cachedFunctions[functionName]; + } + } + }, + + /** + * Enable or disable edit-mode. + * + * @private + */ + _toggleEditMode : function() { + this.editMode = !this.editMode; + var toolbar = document.getElementById("graph-manipulationDiv"); + var closeDiv = document.getElementById("graph-manipulation-closeDiv"); + var editModeDiv = document.getElementById("graph-manipulation-editMode"); + if (this.editMode == true) { + toolbar.style.display="block"; + closeDiv.style.display="block"; + editModeDiv.style.display="none"; + closeDiv.onclick = this._toggleEditMode.bind(this); + } + else { + toolbar.style.display="none"; + closeDiv.style.display="none"; + editModeDiv.style.display="block"; + closeDiv.onclick = null; + } + this._createManipulatorBar() + }, + + /** + * main function, creates the main toolbar. Removes functions bound to the select event. Binds all the buttons of the toolbar. + * + * @private + */ + _createManipulatorBar : function() { + // remove bound functions + if (this.boundFunction) { + this.off('select', this.boundFunction); + } + + // restore overloaded functions + this._restoreOverloadedFunctions(); + + // resume calculation + this.freezeSimulation = false; + + // reset global variables + this.blockConnectingEdgeSelection = false; + this.forceAppendSelection = false + + if (this.editMode == true) { + while (this.manipulationDiv.hasChildNodes()) { + this.manipulationDiv.removeChild(this.manipulationDiv.firstChild); + } + // add the icons to the manipulator div + this.manipulationDiv.innerHTML = "" + + "" + + "Add Node" + + "
" + + "" + + "Add Link"; + if (this._getSelectedNodeCount() == 1 && this.triggerFunctions.edit) { + this.manipulationDiv.innerHTML += "" + + "
" + + "" + + "Edit Node"; + } + if (this._selectionIsEmpty() == false) { + this.manipulationDiv.innerHTML += "" + + "
" + + "" + + "Delete selected"; + } + + + // bind the icons + var addNodeButton = document.getElementById("graph-manipulate-addNode"); + addNodeButton.onclick = this._createAddNodeToolbar.bind(this); + var addEdgeButton = document.getElementById("graph-manipulate-connectNode"); + addEdgeButton.onclick = this._createAddEdgeToolbar.bind(this); + if (this._getSelectedNodeCount() == 1 && this.triggerFunctions.edit) { + var editButton = document.getElementById("graph-manipulate-editNode"); + editButton.onclick = this._editNode.bind(this); + } + if (this._selectionIsEmpty() == false) { + var deleteButton = document.getElementById("graph-manipulate-delete"); + deleteButton.onclick = this._deleteSelected.bind(this); + } + var closeDiv = document.getElementById("graph-manipulation-closeDiv"); + closeDiv.onclick = this._toggleEditMode.bind(this); + + this.boundFunction = this._createManipulatorBar.bind(this); + this.on('select', this.boundFunction); + } + else { + this.editModeDiv.innerHTML = "" + + "" + + "Edit" + var editModeButton = document.getElementById("graph-manipulate-editModeButton"); + editModeButton.onclick = this._toggleEditMode.bind(this); + } + }, + + + + /** + * Create the toolbar for adding Nodes + * + * @private + */ + _createAddNodeToolbar : function() { + // clear the toolbar + this._clearManipulatorBar(); + if (this.boundFunction) { + this.off('select', this.boundFunction); + } + + // create the toolbar contents + this.manipulationDiv.innerHTML = "" + + "" + + "Back" + + "
" + + "" + + "Click in an empty space to place a new node"; + + // bind the icon + var backButton = document.getElementById("graph-manipulate-back"); + backButton.onclick = this._createManipulatorBar.bind(this); + + // we use the boundFunction so we can reference it when we unbind it from the "select" event. + this.boundFunction = this._addNode.bind(this); + this.on('select', this.boundFunction); + }, + + + /** + * create the toolbar to connect nodes + * + * @private + */ + _createAddEdgeToolbar : function() { + // clear the toolbar + this._clearManipulatorBar(); + this._unselectAll(true); + this.freezeSimulation = true; + + if (this.boundFunction) { + this.off('select', this.boundFunction); + } + + this._unselectAll(); + this.forceAppendSelection = false; + this.blockConnectingEdgeSelection = true; + + this.manipulationDiv.innerHTML = "" + + "" + + "Back" + + "
" + + "" + + "Click on a node and drag the edge to another node to connect them."; + + // bind the icon + var backButton = document.getElementById("graph-manipulate-back"); + backButton.onclick = this._createManipulatorBar.bind(this); + + // we use the boundFunction so we can reference it when we unbind it from the "select" event. + this.boundFunction = this._handleConnect.bind(this); + this.on('select', this.boundFunction); + + // temporarily overload functions + this.cachedFunctions["_handleTouch"] = this._handleTouch; + this.cachedFunctions["_handleOnRelease"] = this._handleOnRelease; + this._handleTouch = this._handleConnect; + this._handleOnRelease = this._finishConnect; + + // redraw to show the unselect + this._redraw(); + + }, + + + /** + * the function bound to the selection event. It checks if you want to connect a cluster and changes the description + * to walk the user through the process. + * + * @private + */ + _handleConnect : function(pointer) { + if (this._getSelectedNodeCount() == 0) { + var node = this._getNodeAt(pointer); + if (node != null) { + if (node.clusterSize > 1) { + alert("Cannot create edges to a cluster.") + } + else { + this._selectObject(node,false); + // create a node the temporary line can look at + this.sectors['support']['nodes']['targetNode'] = new Node({id:'targetNode'},{},{},this.constants); + this.sectors['support']['nodes']['targetNode'].x = node.x; + this.sectors['support']['nodes']['targetNode'].y = node.y; + this.sectors['support']['nodes']['targetViaNode'] = new Node({id:'targetViaNode'},{},{},this.constants); + this.sectors['support']['nodes']['targetViaNode'].x = node.x; + this.sectors['support']['nodes']['targetViaNode'].y = node.y; + this.sectors['support']['nodes']['targetViaNode'].parentEdgeId = "connectionEdge"; + + // create a temporary edge + this.edges['connectionEdge'] = new Edge({id:"connectionEdge",from:node.id,to:this.sectors['support']['nodes']['targetNode'].id}, this, this.constants); + this.edges['connectionEdge'].from = node; + this.edges['connectionEdge'].connected = true; + this.edges['connectionEdge'].smooth = true; + this.edges['connectionEdge'].selected = true; + this.edges['connectionEdge'].to = this.sectors['support']['nodes']['targetNode']; + this.edges['connectionEdge'].via = this.sectors['support']['nodes']['targetViaNode']; + + this.cachedFunctions["_handleOnDrag"] = this._handleOnDrag; + this._handleOnDrag = function(event) { + var pointer = this._getPointer(event.gesture.center); + this.sectors['support']['nodes']['targetNode'].x = this._canvasToX(pointer.x); + this.sectors['support']['nodes']['targetNode'].y = this._canvasToY(pointer.y); + this.sectors['support']['nodes']['targetViaNode'].x = 0.5 * (this._canvasToX(pointer.x) + this.edges['connectionEdge'].from.x); + this.sectors['support']['nodes']['targetViaNode'].y = this._canvasToY(pointer.y); + }; + + this.moving = true; + this.start(); + } + } + } + }, + + _finishConnect : function(pointer) { + if (this._getSelectedNodeCount() == 1) { + + // restore the drag function + this._handleOnDrag = this.cachedFunctions["_handleOnDrag"]; + delete this.cachedFunctions["_handleOnDrag"]; + + // remember the edge id + var connectFromId = this.edges['connectionEdge'].fromId; + + // remove the temporary nodes and edge + delete this.edges['connectionEdge'] + delete this.sectors['support']['nodes']['targetNode']; + delete this.sectors['support']['nodes']['targetViaNode']; + + var node = this._getNodeAt(pointer); + if (node != null) { + if (node.clusterSize > 1) { + alert("Cannot create edges to a cluster.") + } + else { + this._createEdge(connectFromId,node.id); + this._createManipulatorBar(); + } + } + this._unselectAll(); + } + }, + + + /** + * Adds a node on the specified location + * + * @param {Object} pointer + */ + _addNode : function() { + if (this._selectionIsEmpty() && this.editMode == true) { + var positionObject = this._pointerToPositionObject(this.pointerPosition); + var defaultData = {id:util.randomUUID(),x:positionObject.left,y:positionObject.top,label:"new",allowedToMoveX:true,allowedToMoveY:true}; + if (this.triggerFunctions.add) { + if (this.triggerFunctions.add.length == 2) { + var me = this; + this.triggerFunctions.add(defaultData, function(finalizedData) { + me.nodesData.add(finalizedData); + me._createManipulatorBar(); + me.moving = true; + me.start(); + }); + } + else { + alert("The function for add does not support two arguments (data,callback)."); + this._createManipulatorBar(); + this.moving = true; + this.start(); + } + } + else { + this.nodesData.add(defaultData); + this._createManipulatorBar(); + this.moving = true; + this.start(); + } + } + }, + + + /** + * connect two nodes with a new edge. + * + * @private + */ + _createEdge : function(sourceNodeId,targetNodeId) { + if (this.editMode == true) { + var defaultData = {from:sourceNodeId, to:targetNodeId}; + if (this.triggerFunctions.connect) { + if (this.triggerFunctions.connect.length == 2) { + var me = this; + this.triggerFunctions.connect(defaultData, function(finalizedData) { + me.edgesData.add(finalizedData) + me.moving = true; + me.start(); + }); + } + else { + alert("The function for connect does not support two arguments (data,callback)."); + this.moving = true; + this.start(); + } + } + else { + this.edgesData.add(defaultData) + this.moving = true; + this.start(); + } + } + }, + + + /** + * Create the toolbar to edit the selected node. The label and the color can be changed. Other colors are derived from the chosen color. + * + * @private + */ + _editNode : function() { + if (this.triggerFunctions.edit && this.editMode == true) { + var node = this._getSelectedNode(); + var data = {id:node.id, + label: node.label, + group: node.group, + shape: node.shape, + color: { + background:node.color.background, + border:node.color.border, + highlight: { + background:node.color.highlight.background, + border:node.color.highlight.border + } + }}; + if (this.triggerFunctions.edit.length == 2) { + var me = this; + this.triggerFunctions.edit(data, function (finalizedData) { + me.nodesData.update(finalizedData); + me._createManipulatorBar(); + me.moving = true; + me.start(); + }); + } + else { + alert("The function for edit does not support two arguments (data, callback).") + } + } + else { + alert("No edit function has been bound to this button.") + } + }, + + + /** + * delete everything in the selection + * + * @private + */ + _deleteSelected : function() { + if (!this._selectionIsEmpty() && this.editMode == true) { + if (!this._clusterInSelection()) { + var selectedNodes = this.getSelectedNodes(); + var selectedEdges = this.getSelectedEdges(); + if (this.triggerFunctions.delete) { + var me = this; + var data = {nodes: selectedNodes, edges: selectedEdges}; + if (this.triggerFunctions.delete.length = 2) { + this.triggerFunctions.delete(data, function (finalizedData) { + me.edgesData.remove(finalizedData.edges); + me.nodesData.remove(finalizedData.nodes); + this._unselectAll(); + me.moving = true; + me.start(); + }); + } + else { + alert("The function for edit does not support two arguments (data, callback).") + } + } + else { + this.edgesData.remove(selectedEdges); + this.nodesData.remove(selectedNodes); + this._unselectAll(); + this.moving = true; + this.start(); + } + } + else { + alert("Clusters cannot be deleted."); + } + } + } +}; +/** + * Creation of the SectorMixin var. + * + * This contains all the functions the Graph object can use to employ the sector system. + * The sector system is always used by Graph, though the benefits only apply to the use of clustering. + * If clustering is not used, there is no overhead except for a duplicate object with references to nodes and edges. + * + * Alex de Mulder + * 21-01-2013 + */ +var SectorMixin = { + + /** + * This function is only called by the setData function of the Graph object. + * This loads the global references into the active sector. This initializes the sector. + * + * @private + */ + _putDataInSector : function() { + this.sectors["active"][this._sector()].nodes = this.nodes; + this.sectors["active"][this._sector()].edges = this.edges; + this.sectors["active"][this._sector()].nodeIndices = this.nodeIndices; + }, + + + /** + * /** + * This function sets the global references to nodes, edges and nodeIndices back to + * those of the supplied (active) sector. If a type is defined, do the specific type + * + * @param {String} sectorId + * @param {String} [sectorType] | "active" or "frozen" + * @private + */ + _switchToSector : function(sectorId, sectorType) { + if (sectorType === undefined || sectorType == "active") { + this._switchToActiveSector(sectorId); + } + else { + this._switchToFrozenSector(sectorId); + } + }, + + + /** + * This function sets the global references to nodes, edges and nodeIndices back to + * those of the supplied active sector. + * + * @param sectorId + * @private + */ + _switchToActiveSector : function(sectorId) { + this.nodeIndices = this.sectors["active"][sectorId]["nodeIndices"]; + this.nodes = this.sectors["active"][sectorId]["nodes"]; + this.edges = this.sectors["active"][sectorId]["edges"]; + }, + + + /** + * This function sets the global references to nodes, edges and nodeIndices back to + * those of the supplied active sector. + * + * @param sectorId + * @private + */ + _switchToSupportSector : function() { + this.nodeIndices = this.sectors["support"]["nodeIndices"]; + this.nodes = this.sectors["support"]["nodes"]; + this.edges = this.sectors["support"]["edges"]; + }, + + + /** + * This function sets the global references to nodes, edges and nodeIndices back to + * those of the supplied frozen sector. + * + * @param sectorId + * @private + */ + _switchToFrozenSector : function(sectorId) { + this.nodeIndices = this.sectors["frozen"][sectorId]["nodeIndices"]; + this.nodes = this.sectors["frozen"][sectorId]["nodes"]; + this.edges = this.sectors["frozen"][sectorId]["edges"]; + }, + + + /** + * This function sets the global references to nodes, edges and nodeIndices back to + * those of the currently active sector. + * + * @private + */ + _loadLatestSector : function() { + this._switchToSector(this._sector()); + }, + + + /** + * This function returns the currently active sector Id + * + * @returns {String} + * @private + */ + _sector : function() { + return this.activeSector[this.activeSector.length-1]; + }, + + + /** + * This function returns the previously active sector Id + * + * @returns {String} + * @private + */ + _previousSector : function() { + if (this.activeSector.length > 1) { + return this.activeSector[this.activeSector.length-2]; + } + else { + throw new TypeError('there are not enough sectors in the this.activeSector array.'); + } + }, + + + /** + * We add the active sector at the end of the this.activeSector array + * This ensures it is the currently active sector returned by _sector() and it reaches the top + * of the activeSector stack. When we reverse our steps we move from the end to the beginning of this stack. + * + * @param newId + * @private + */ + _setActiveSector : function(newId) { + this.activeSector.push(newId); + }, + + + /** + * We remove the currently active sector id from the active sector stack. This happens when + * we reactivate the previously active sector + * + * @private + */ + _forgetLastSector : function() { + this.activeSector.pop(); + }, + + + /** + * This function creates a new active sector with the supplied newId. This newId + * is the expanding node id. + * + * @param {String} newId | Id of the new active sector + * @private + */ + _createNewSector : function(newId) { + // create the new sector + this.sectors["active"][newId] = {"nodes":{}, + "edges":{}, + "nodeIndices":[], + "formationScale": this.scale, + "drawingNode": undefined}; + + // create the new sector render node. This gives visual feedback that you are in a new sector. + this.sectors["active"][newId]['drawingNode'] = new Node( + {id:newId, + color: { + background: "#eaefef", + border: "495c5e" + } + },{},{},this.constants); + this.sectors["active"][newId]['drawingNode'].clusterSize = 2; + }, + + + /** + * This function removes the currently active sector. This is called when we create a new + * active sector. + * + * @param {String} sectorId | Id of the active sector that will be removed + * @private + */ + _deleteActiveSector : function(sectorId) { + delete this.sectors["active"][sectorId]; + }, + + + /** + * This function removes the currently active sector. This is called when we reactivate + * the previously active sector. + * + * @param {String} sectorId | Id of the active sector that will be removed + * @private + */ + _deleteFrozenSector : function(sectorId) { + delete this.sectors["frozen"][sectorId]; + }, + + + /** + * Freezing an active sector means moving it from the "active" object to the "frozen" object. + * We copy the references, then delete the active entree. + * + * @param sectorId + * @private + */ + _freezeSector : function(sectorId) { + // we move the set references from the active to the frozen stack. + this.sectors["frozen"][sectorId] = this.sectors["active"][sectorId]; + + // we have moved the sector data into the frozen set, we now remove it from the active set + this._deleteActiveSector(sectorId); + }, + + + /** + * This is the reverse operation of _freezeSector. Activating means moving the sector from the "frozen" + * object to the "active" object. + * + * @param sectorId + * @private + */ + _activateSector : function(sectorId) { + // we move the set references from the frozen to the active stack. + this.sectors["active"][sectorId] = this.sectors["frozen"][sectorId]; + + // we have moved the sector data into the active set, we now remove it from the frozen stack + this._deleteFrozenSector(sectorId); + }, + + + /** + * This function merges the data from the currently active sector with a frozen sector. This is used + * in the process of reverting back to the previously active sector. + * The data that is placed in the frozen (the previously active) sector is the node that has been removed from it + * upon the creation of a new active sector. + * + * @param sectorId + * @private + */ + _mergeThisWithFrozen : function(sectorId) { + // copy all nodes + for (var nodeId in this.nodes) { + if (this.nodes.hasOwnProperty(nodeId)) { + this.sectors["frozen"][sectorId]["nodes"][nodeId] = this.nodes[nodeId]; + } + } + + // copy all edges (if not fully clustered, else there are no edges) + for (var edgeId in this.edges) { + if (this.edges.hasOwnProperty(edgeId)) { + this.sectors["frozen"][sectorId]["edges"][edgeId] = this.edges[edgeId]; + } + } + + // merge the nodeIndices + for (var i = 0; i < this.nodeIndices.length; i++) { + this.sectors["frozen"][sectorId]["nodeIndices"].push(this.nodeIndices[i]); + } + }, + + + /** + * This clusters the sector to one cluster. It was a single cluster before this process started so + * we revert to that state. The clusterToFit function with a maximum size of 1 node does this. + * + * @private + */ + _collapseThisToSingleCluster : function() { + this.clusterToFit(1,false); + }, + + + /** + * We create a new active sector from the node that we want to open. + * + * @param node + * @private + */ + _addSector : function(node) { + // this is the currently active sector + var sector = this._sector(); + +// // this should allow me to select nodes from a frozen set. +// if (this.sectors['active'][sector]["nodes"].hasOwnProperty(node.id)) { +// console.log("the node is part of the active sector"); +// } +// else { +// console.log("I dont know what the fuck happened!!"); +// } + + // when we switch to a new sector, we remove the node that will be expanded from the current nodes list. + delete this.nodes[node.id]; + + var unqiueIdentifier = util.randomUUID(); + + // we fully freeze the currently active sector + this._freezeSector(sector); + + // we create a new active sector. This sector has the Id of the node to ensure uniqueness + this._createNewSector(unqiueIdentifier); + + // we add the active sector to the sectors array to be able to revert these steps later on + this._setActiveSector(unqiueIdentifier); + + // we redirect the global references to the new sector's references. this._sector() now returns unqiueIdentifier + this._switchToSector(this._sector()); + + // finally we add the node we removed from our previous active sector to the new active sector + this.nodes[node.id] = node; + }, + + + /** + * We close the sector that is currently open and revert back to the one before. + * If the active sector is the "default" sector, nothing happens. + * + * @private + */ + _collapseSector : function() { + // the currently active sector + var sector = this._sector(); + + // we cannot collapse the default sector + if (sector != "default") { + if ((this.nodeIndices.length == 1) || + (this.sectors["active"][sector]["drawingNode"].width*this.scale < this.constants.clustering.screenSizeThreshold * this.frame.canvas.clientWidth) || + (this.sectors["active"][sector]["drawingNode"].height*this.scale < this.constants.clustering.screenSizeThreshold * this.frame.canvas.clientHeight)) { + var previousSector = this._previousSector(); + + // we collapse the sector back to a single cluster + this._collapseThisToSingleCluster(); + + // we move the remaining nodes, edges and nodeIndices to the previous sector. + // This previous sector is the one we will reactivate + this._mergeThisWithFrozen(previousSector); + + // the previously active (frozen) sector now has all the data from the currently active sector. + // we can now delete the active sector. + this._deleteActiveSector(sector); + + // we activate the previously active (and currently frozen) sector. + this._activateSector(previousSector); + + // we load the references from the newly active sector into the global references + this._switchToSector(previousSector); + + // we forget the previously active sector because we reverted to the one before + this._forgetLastSector(); + + // finally, we update the node index list. + this._updateNodeIndexList(); + + // we refresh the list with calulation nodes and calculation node indices. + this._updateCalculationNodes(); + } + } + }, + + + /** + * This runs a function in all active sectors. This is used in _redraw() and the _initializeForceCalculation(). + * + * @param {String} runFunction | This is the NAME of a function we want to call in all active sectors + * | we dont pass the function itself because then the "this" is the window object + * | instead of the Graph object + * @param {*} [argument] | Optional: arguments to pass to the runFunction + * @private + */ + _doInAllActiveSectors : function(runFunction,argument) { + if (argument === undefined) { + for (var sector in this.sectors["active"]) { + if (this.sectors["active"].hasOwnProperty(sector)) { + // switch the global references to those of this sector + this._switchToActiveSector(sector); + this[runFunction](); + } + } + } + else { + for (var sector in this.sectors["active"]) { + if (this.sectors["active"].hasOwnProperty(sector)) { + // switch the global references to those of this sector + this._switchToActiveSector(sector); + var args = Array.prototype.splice.call(arguments, 1); + if (args.length > 1) { + this[runFunction](args[0],args[1]); + } + else { + this[runFunction](argument); + } + } + } + } + // we revert the global references back to our active sector + this._loadLatestSector(); + }, + + + /** + * This runs a function in all active sectors. This is used in _redraw() and the _initializeForceCalculation(). + * + * @param {String} runFunction | This is the NAME of a function we want to call in all active sectors + * | we dont pass the function itself because then the "this" is the window object + * | instead of the Graph object + * @param {*} [argument] | Optional: arguments to pass to the runFunction + * @private + */ + _doInSupportSector : function(runFunction,argument) { + if (argument === undefined) { + this._switchToSupportSector(); + this[runFunction](); + } + else { + this._switchToSupportSector(); + var args = Array.prototype.splice.call(arguments, 1); + if (args.length > 1) { + this[runFunction](args[0],args[1]); + } + else { + this[runFunction](argument); + } + } + // we revert the global references back to our active sector + this._loadLatestSector(); + }, + + + /** + * This runs a function in all frozen sectors. This is used in the _redraw(). + * + * @param {String} runFunction | This is the NAME of a function we want to call in all active sectors + * | we don't pass the function itself because then the "this" is the window object + * | instead of the Graph object + * @param {*} [argument] | Optional: arguments to pass to the runFunction + * @private + */ + _doInAllFrozenSectors : function(runFunction,argument) { + if (argument === undefined) { + for (var sector in this.sectors["frozen"]) { + if (this.sectors["frozen"].hasOwnProperty(sector)) { + // switch the global references to those of this sector + this._switchToFrozenSector(sector); + this[runFunction](); + } + } + } + else { + for (var sector in this.sectors["frozen"]) { + if (this.sectors["frozen"].hasOwnProperty(sector)) { + // switch the global references to those of this sector + this._switchToFrozenSector(sector); + var args = Array.prototype.splice.call(arguments, 1); + if (args.length > 1) { + this[runFunction](args[0],args[1]); + } + else { + this[runFunction](argument); + } + } + } + } + this._loadLatestSector(); + }, + + + /** + * This runs a function in all sectors. This is used in the _redraw(). + * + * @param {String} runFunction | This is the NAME of a function we want to call in all active sectors + * | we don't pass the function itself because then the "this" is the window object + * | instead of the Graph object + * @param {*} [argument] | Optional: arguments to pass to the runFunction + * @private + */ + _doInAllSectors : function(runFunction,argument) { + var args = Array.prototype.splice.call(arguments, 1); + if (argument === undefined) { + this._doInAllActiveSectors(runFunction); + this._doInAllFrozenSectors(runFunction); + } + else { + if (args.length > 1) { + this._doInAllActiveSectors(runFunction,args[0],args[1]); + this._doInAllFrozenSectors(runFunction,args[0],args[1]); + } + else { + this._doInAllActiveSectors(runFunction,argument); + this._doInAllFrozenSectors(runFunction,argument); + } + } + }, + + + /** + * This clears the nodeIndices list. We cannot use this.nodeIndices = [] because we would break the link with the + * active sector. Thus we clear the nodeIndices in the active sector, then reconnect the this.nodeIndices to it. + * + * @private + */ + _clearNodeIndexList : function() { + var sector = this._sector(); + this.sectors["active"][sector]["nodeIndices"] = []; + this.nodeIndices = this.sectors["active"][sector]["nodeIndices"]; + }, + + + /** + * Draw the encompassing sector node + * + * @param ctx + * @param sectorType + * @private + */ + _drawSectorNodes : function(ctx,sectorType) { + var minY = 1e9, maxY = -1e9, minX = 1e9, maxX = -1e9, node; + for (var sector in this.sectors[sectorType]) { + if (this.sectors[sectorType].hasOwnProperty(sector)) { + if (this.sectors[sectorType][sector]["drawingNode"] !== undefined) { + + this._switchToSector(sector,sectorType); + + minY = 1e9; maxY = -1e9; minX = 1e9; maxX = -1e9; + for (var nodeId in this.nodes) { + if (this.nodes.hasOwnProperty(nodeId)) { + node = this.nodes[nodeId]; + node.resize(ctx); + if (minX > node.x - 0.5 * node.width) {minX = node.x - 0.5 * node.width;} + if (maxX < node.x + 0.5 * node.width) {maxX = node.x + 0.5 * node.width;} + if (minY > node.y - 0.5 * node.height) {minY = node.y - 0.5 * node.height;} + if (maxY < node.y + 0.5 * node.height) {maxY = node.y + 0.5 * node.height;} + } + } + node = this.sectors[sectorType][sector]["drawingNode"]; + node.x = 0.5 * (maxX + minX); + node.y = 0.5 * (maxY + minY); + node.width = 2 * (node.x - minX); + node.height = 2 * (node.y - minY); + node.radius = Math.sqrt(Math.pow(0.5*node.width,2) + Math.pow(0.5*node.height,2)); + node.setScale(this.scale); + node._drawCircle(ctx); + } + } + } + }, + + _drawAllSectorNodes : function(ctx) { + this._drawSectorNodes(ctx,"frozen"); + this._drawSectorNodes(ctx,"active"); + this._loadLatestSector(); + } +}; +/** + * Creation of the ClusterMixin var. + * + * This contains all the functions the Graph object can use to employ clustering + * + * Alex de Mulder + * 21-01-2013 + */ +var ClusterMixin = { + + /** + * This is only called in the constructor of the graph object + * + */ + startWithClustering : function() { + // cluster if the data set is big + this.clusterToFit(this.constants.clustering.initialMaxNodes, true); + + // updates the lables after clustering + this.updateLabels(); + + // this is called here because if clusterin is disabled, the start and stabilize are called in + // the setData function. + if (this.stabilize) { + this._stabilize(); + } + this.start(); + }, + + /** + * This function clusters until the initialMaxNodes has been reached + * + * @param {Number} maxNumberOfNodes + * @param {Boolean} reposition + */ + clusterToFit : function(maxNumberOfNodes, reposition) { + var numberOfNodes = this.nodeIndices.length; + + var maxLevels = 50; + var level = 0; + + // we first cluster the hubs, then we pull in the outliers, repeat + while (numberOfNodes > maxNumberOfNodes && level < maxLevels) { + if (level % 3 == 0) { + this.forceAggregateHubs(true); + this.normalizeClusterLevels(); + } + else { + this.increaseClusterLevel(); // this also includes a cluster normalization + } + + numberOfNodes = this.nodeIndices.length; + level += 1; + } + + // after the clustering we reposition the nodes to reduce the initial chaos + if (level > 0 && reposition == true) { + this.repositionNodes(); + } + this._updateCalculationNodes(); + }, + + /** + * This function can be called to open up a specific cluster. It is only called by + * It will unpack the cluster back one level. + * + * @param node | Node object: cluster to open. + */ + openCluster : function(node) { + var isMovingBeforeClustering = this.moving; + if (node.clusterSize > this.constants.clustering.sectorThreshold && this._nodeInActiveArea(node) && + !(this._sector() == "default" && this.nodeIndices.length == 1)) { + // this loads a new sector, loads the nodes and edges and nodeIndices of it. + this._addSector(node); + var level = 0; + + // we decluster until we reach a decent number of nodes + while ((this.nodeIndices.length < this.constants.clustering.initialMaxNodes) && (level < 10)) { + this.decreaseClusterLevel(); + level += 1; + } + + } + else { + this._expandClusterNode(node,false,true); + + // update the index list, dynamic edges and labels + this._updateNodeIndexList(); + this._updateDynamicEdges(); + this._updateCalculationNodes(); + this.updateLabels(); + } + + // if the simulation was settled, we restart the simulation if a cluster has been formed or expanded + if (this.moving != isMovingBeforeClustering) { + this.start(); + } + }, + + + /** + * This calls the updateClustes with default arguments + */ + updateClustersDefault : function() { + if (this.constants.clustering.enabled == true) { + this.updateClusters(0,false,false); + } + }, + + + /** + * This function can be called to increase the cluster level. This means that the nodes with only one edge connection will + * be clustered with their connected node. This can be repeated as many times as needed. + * This can be called externally (by a keybind for instance) to reduce the complexity of big datasets. + */ + increaseClusterLevel : function() { + this.updateClusters(-1,false,true); + }, + + + /** + * This function can be called to decrease the cluster level. This means that the nodes with only one edge connection will + * be unpacked if they are a cluster. This can be repeated as many times as needed. + * This can be called externally (by a key-bind for instance) to look into clusters without zooming. + */ + decreaseClusterLevel : function() { + this.updateClusters(1,false,true); + }, + + + /** + * This is the main clustering function. It clusters and declusters on zoom or forced + * This function clusters on zoom, it can be called with a predefined zoom direction + * If out, check if we can form clusters, if in, check if we can open clusters. + * This function is only called from _zoom() + * + * @param {Number} zoomDirection | -1 / 0 / +1 for zoomOut / determineByZoom / zoomIn + * @param {Boolean} recursive | enabled or disable recursive calling of the opening of clusters + * @param {Boolean} force | enabled or disable forcing + * + */ + updateClusters : function(zoomDirection,recursive,force,doNotStart) { + var isMovingBeforeClustering = this.moving; + var amountOfNodes = this.nodeIndices.length; + + // on zoom out collapse the sector if the scale is at the level the sector was made + if (this.previousScale > this.scale && zoomDirection == 0) { + this._collapseSector(); + } + + // check if we zoom in or out + if (this.previousScale > this.scale || zoomDirection == -1) { // zoom out + // forming clusters when forced pulls outliers in. When not forced, the edge length of the + // outer nodes determines if it is being clustered + this._formClusters(force); + } + else if (this.previousScale < this.scale || zoomDirection == 1) { // zoom in + if (force == true) { + // _openClusters checks for each node if the formationScale of the cluster is smaller than + // the current scale and if so, declusters. When forced, all clusters are reduced by one step + this._openClusters(recursive,force); + } + else { + // if a cluster takes up a set percentage of the active window + this._openClustersBySize(); + } + } + this._updateNodeIndexList(); + + // if a cluster was NOT formed and the user zoomed out, we try clustering by hubs + if (this.nodeIndices.length == amountOfNodes && (this.previousScale > this.scale || zoomDirection == -1)) { + this._aggregateHubs(force); + this._updateNodeIndexList(); + } + + // we now reduce chains. + if (this.previousScale > this.scale || zoomDirection == -1) { // zoom out + this.handleChains(); + this._updateNodeIndexList(); + } + + this.previousScale = this.scale; + + // rest of the update the index list, dynamic edges and labels + this._updateDynamicEdges(); + this.updateLabels(); + + // if a cluster was formed, we increase the clusterSession + if (this.nodeIndices.length < amountOfNodes) { // this means a clustering operation has taken place + this.clusterSession += 1; + // if clusters have been made, we normalize the cluster level + this.normalizeClusterLevels(); + } + + if (doNotStart == false || doNotStart === undefined) { + // if the simulation was settled, we restart the simulation if a cluster has been formed or expanded + if (this.moving != isMovingBeforeClustering) { + this.start(); + } + } + + this._updateCalculationNodes(); + }, + + /** + * This function handles the chains. It is called on every updateClusters(). + */ + handleChains : function() { + // after clustering we check how many chains there are + var chainPercentage = this._getChainFraction(); + if (chainPercentage > this.constants.clustering.chainThreshold) { + this._reduceAmountOfChains(1 - this.constants.clustering.chainThreshold / chainPercentage) + + } + }, + + /** + * this functions starts clustering by hubs + * The minimum hub threshold is set globally + * + * @private + */ + _aggregateHubs : function(force) { + this._getHubSize(); + this._formClustersByHub(force,false); + }, + + + /** + * This function is fired by keypress. It forces hubs to form. + * + */ + forceAggregateHubs : function(doNotStart) { + var isMovingBeforeClustering = this.moving; + var amountOfNodes = this.nodeIndices.length; + + this._aggregateHubs(true); + + // update the index list, dynamic edges and labels + this._updateNodeIndexList(); + this._updateDynamicEdges(); + this.updateLabels(); + + // if a cluster was formed, we increase the clusterSession + if (this.nodeIndices.length != amountOfNodes) { + this.clusterSession += 1; + } + + if (doNotStart == false || doNotStart === undefined) { + // if the simulation was settled, we restart the simulation if a cluster has been formed or expanded + if (this.moving != isMovingBeforeClustering) { + this.start(); + } + } + }, + + /** + * If a cluster takes up more than a set percentage of the screen, open the cluster + * + * @private + */ + _openClustersBySize : function() { + for (var nodeId in this.nodes) { + if (this.nodes.hasOwnProperty(nodeId)) { + var node = this.nodes[nodeId]; + if (node.inView() == true) { + if ((node.width*this.scale > this.constants.clustering.screenSizeThreshold * this.frame.canvas.clientWidth) || + (node.height*this.scale > this.constants.clustering.screenSizeThreshold * this.frame.canvas.clientHeight)) { + this.openCluster(node); + } + } + } + } + }, + + + /** + * This function loops over all nodes in the nodeIndices list. For each node it checks if it is a cluster and if it + * has to be opened based on the current zoom level. + * + * @private + */ + _openClusters : function(recursive,force) { + for (var i = 0; i < this.nodeIndices.length; i++) { + var node = this.nodes[this.nodeIndices[i]]; + this._expandClusterNode(node,recursive,force); + this._updateCalculationNodes(); + } + }, + + /** + * This function checks if a node has to be opened. This is done by checking the zoom level. + * If the node contains child nodes, this function is recursively called on the child nodes as well. + * This recursive behaviour is optional and can be set by the recursive argument. + * + * @param {Node} parentNode | to check for cluster and expand + * @param {Boolean} recursive | enabled or disable recursive calling + * @param {Boolean} force | enabled or disable forcing + * @param {Boolean} [openAll] | This will recursively force all nodes in the parent to be released + * @private + */ + _expandClusterNode : function(parentNode, recursive, force, openAll) { + // first check if node is a cluster + if (parentNode.clusterSize > 1) { + // this means that on a double tap event or a zoom event, the cluster fully unpacks if it is smaller than 20 + if (parentNode.clusterSize < this.constants.clustering.sectorThreshold) { + openAll = true; + } + recursive = openAll ? true : recursive; + + // if the last child has been added on a smaller scale than current scale decluster + if (parentNode.formationScale < this.scale || force == true) { + // we will check if any of the contained child nodes should be removed from the cluster + for (var containedNodeId in parentNode.containedNodes) { + if (parentNode.containedNodes.hasOwnProperty(containedNodeId)) { + var childNode = parentNode.containedNodes[containedNodeId]; + + // force expand will expand the largest cluster size clusters. Since we cluster from outside in, we assume that + // the largest cluster is the one that comes from outside + if (force == true) { + if (childNode.clusterSession == parentNode.clusterSessions[parentNode.clusterSessions.length-1] + || openAll) { + this._expelChildFromParent(parentNode,containedNodeId,recursive,force,openAll); + } + } + else { + if (this._nodeInActiveArea(parentNode)) { + this._expelChildFromParent(parentNode,containedNodeId,recursive,force,openAll); + } + } + } + } + } + } + }, + + /** + * ONLY CALLED FROM _expandClusterNode + * + * This function will expel a child_node from a parent_node. This is to de-cluster the node. This function will remove + * the child node from the parent contained_node object and put it back into the global nodes object. + * The same holds for the edge that was connected to the child node. It is moved back into the global edges object. + * + * @param {Node} parentNode | the parent node + * @param {String} containedNodeId | child_node id as it is contained in the containedNodes object of the parent node + * @param {Boolean} recursive | This will also check if the child needs to be expanded. + * With force and recursive both true, the entire cluster is unpacked + * @param {Boolean} force | This will disregard the zoom level and will expel this child from the parent + * @param {Boolean} openAll | This will recursively force all nodes in the parent to be released + * @private + */ + _expelChildFromParent : function(parentNode, containedNodeId, recursive, force, openAll) { + var childNode = parentNode.containedNodes[containedNodeId]; + + // if child node has been added on smaller scale than current, kick out + if (childNode.formationScale < this.scale || force == true) { + // unselect all selected items + this._unselectAll(); + + // put the child node back in the global nodes object + this.nodes[containedNodeId] = childNode; + + // release the contained edges from this childNode back into the global edges + this._releaseContainedEdges(parentNode,childNode); + + // reconnect rerouted edges to the childNode + this._connectEdgeBackToChild(parentNode,childNode); + + // validate all edges in dynamicEdges + this._validateEdges(parentNode); + + // undo the changes from the clustering operation on the parent node + parentNode.mass -= childNode.mass; + parentNode.clusterSize -= childNode.clusterSize; + parentNode.fontSize = Math.min(this.constants.clustering.maxFontSize, this.constants.nodes.fontSize + this.constants.clustering.fontSizeMultiplier*parentNode.clusterSize); + parentNode.dynamicEdgesLength = parentNode.dynamicEdges.length; + + // place the child node near the parent, not at the exact same location to avoid chaos in the system + childNode.x = parentNode.x + parentNode.growthIndicator * (0.5 - Math.random()); + childNode.y = parentNode.y + parentNode.growthIndicator * (0.5 - Math.random()); + + // remove node from the list + delete parentNode.containedNodes[containedNodeId]; + + // check if there are other childs with this clusterSession in the parent. + var othersPresent = false; + for (var childNodeId in parentNode.containedNodes) { + if (parentNode.containedNodes.hasOwnProperty(childNodeId)) { + if (parentNode.containedNodes[childNodeId].clusterSession == childNode.clusterSession) { + othersPresent = true; + break; + } + } + } + // if there are no others, remove the cluster session from the list + if (othersPresent == false) { + parentNode.clusterSessions.pop(); + } + + this._repositionBezierNodes(childNode); +// this._repositionBezierNodes(parentNode); + + // remove the clusterSession from the child node + childNode.clusterSession = 0; + + // recalculate the size of the node on the next time the node is rendered + parentNode.clearSizeCache(); + + // restart the simulation to reorganise all nodes + this.moving = true; + } + + // check if a further expansion step is possible if recursivity is enabled + if (recursive == true) { + this._expandClusterNode(childNode,recursive,force,openAll); + } + }, + + + /** + * position the bezier nodes at the center of the edges + * + * @param node + * @private + */ + _repositionBezierNodes : function(node) { + for (var i = 0; i < node.dynamicEdges.length; i++) { + node.dynamicEdges[i].positionBezierNode(); + } + }, + + + /** + * This function checks if any nodes at the end of their trees have edges below a threshold length + * This function is called only from updateClusters() + * forceLevelCollapse ignores the length of the edge and collapses one level + * This means that a node with only one edge will be clustered with its connected node + * + * @private + * @param {Boolean} force + */ + _formClusters : function(force) { + if (force == false) { + this._formClustersByZoom(); + } + else { + this._forceClustersByZoom(); + } + }, + + + /** + * This function handles the clustering by zooming out, this is based on a minimum edge distance + * + * @private + */ + _formClustersByZoom : function() { + var dx,dy,length, + minLength = this.constants.clustering.clusterEdgeThreshold/this.scale; + + // check if any edges are shorter than minLength and start the clustering + // the clustering favours the node with the larger mass + for (var edgeId in this.edges) { + if (this.edges.hasOwnProperty(edgeId)) { + var edge = this.edges[edgeId]; + if (edge.connected) { + if (edge.toId != edge.fromId) { + dx = (edge.to.x - edge.from.x); + dy = (edge.to.y - edge.from.y); + length = Math.sqrt(dx * dx + dy * dy); + + + if (length < minLength) { + // first check which node is larger + var parentNode = edge.from; + var childNode = edge.to; + if (edge.to.mass > edge.from.mass) { + parentNode = edge.to; + childNode = edge.from; + } + + if (childNode.dynamicEdgesLength == 1) { + this._addToCluster(parentNode,childNode,false); + } + else if (parentNode.dynamicEdgesLength == 1) { + this._addToCluster(childNode,parentNode,false); + } + } + } + } + } + } + }, + + /** + * This function forces the graph to cluster all nodes with only one connecting edge to their + * connected node. + * + * @private + */ + _forceClustersByZoom : function() { + for (var nodeId in this.nodes) { + // another node could have absorbed this child. + if (this.nodes.hasOwnProperty(nodeId)) { + var childNode = this.nodes[nodeId]; + + // the edges can be swallowed by another decrease + if (childNode.dynamicEdgesLength == 1 && childNode.dynamicEdges.length != 0) { + var edge = childNode.dynamicEdges[0]; + var parentNode = (edge.toId == childNode.id) ? this.nodes[edge.fromId] : this.nodes[edge.toId]; + + // group to the largest node + if (childNode.id != parentNode.id) { + if (parentNode.mass > childNode.mass) { + this._addToCluster(parentNode,childNode,true); + } + else { + this._addToCluster(childNode,parentNode,true); + } + } + } + } + } + }, + + + /** + * To keep the nodes of roughly equal size we normalize the cluster levels. + * This function clusters a node to its smallest connected neighbour. + * + * @param node + * @private + */ + _clusterToSmallestNeighbour : function(node) { + var smallestNeighbour = -1; + var smallestNeighbourNode = null; + for (var i = 0; i < node.dynamicEdges.length; i++) { + if (node.dynamicEdges[i] !== undefined) { + var neighbour = null; + if (node.dynamicEdges[i].fromId != node.id) { + neighbour = node.dynamicEdges[i].from; + } + else if (node.dynamicEdges[i].toId != node.id) { + neighbour = node.dynamicEdges[i].to; + } + + + if (neighbour != null && smallestNeighbour > neighbour.clusterSessions.length) { + smallestNeighbour = neighbour.clusterSessions.length; + smallestNeighbourNode = neighbour; + } + } + } + + if (neighbour != null && this.nodes[neighbour.id] !== undefined) { + this._addToCluster(neighbour, node, true); + } + }, + + + /** + * This function forms clusters from hubs, it loops over all nodes + * + * @param {Boolean} force | Disregard zoom level + * @param {Boolean} onlyEqual | This only clusters a hub with a specific number of edges + * @private + */ + _formClustersByHub : function(force, onlyEqual) { + // we loop over all nodes in the list + for (var nodeId in this.nodes) { + // we check if it is still available since it can be used by the clustering in this loop + if (this.nodes.hasOwnProperty(nodeId)) { + this._formClusterFromHub(this.nodes[nodeId],force,onlyEqual); + } + } + }, + + /** + * This function forms a cluster from a specific preselected hub node + * + * @param {Node} hubNode | the node we will cluster as a hub + * @param {Boolean} force | Disregard zoom level + * @param {Boolean} onlyEqual | This only clusters a hub with a specific number of edges + * @param {Number} [absorptionSizeOffset] | + * @private + */ + _formClusterFromHub : function(hubNode, force, onlyEqual, absorptionSizeOffset) { + if (absorptionSizeOffset === undefined) { + absorptionSizeOffset = 0; + } + // we decide if the node is a hub + if ((hubNode.dynamicEdgesLength >= this.hubThreshold && onlyEqual == false) || + (hubNode.dynamicEdgesLength == this.hubThreshold && onlyEqual == true)) { + // initialize variables + var dx,dy,length; + var minLength = this.constants.clustering.clusterEdgeThreshold/this.scale; + var allowCluster = false; + + // we create a list of edges because the dynamicEdges change over the course of this loop + var edgesIdarray = []; + var amountOfInitialEdges = hubNode.dynamicEdges.length; + for (var j = 0; j < amountOfInitialEdges; j++) { + edgesIdarray.push(hubNode.dynamicEdges[j].id); + } + + // if the hub clustering is not forces, we check if one of the edges connected + // to a cluster is small enough based on the constants.clustering.clusterEdgeThreshold + if (force == false) { + allowCluster = false; + for (j = 0; j < amountOfInitialEdges; j++) { + var edge = this.edges[edgesIdarray[j]]; + if (edge !== undefined) { + if (edge.connected) { + if (edge.toId != edge.fromId) { + dx = (edge.to.x - edge.from.x); + dy = (edge.to.y - edge.from.y); + length = Math.sqrt(dx * dx + dy * dy); + + if (length < minLength) { + allowCluster = true; + break; + } + } + } + } + } + } + + // start the clustering if allowed + if ((!force && allowCluster) || force) { + // we loop over all edges INITIALLY connected to this hub + for (j = 0; j < amountOfInitialEdges; j++) { + edge = this.edges[edgesIdarray[j]]; + // the edge can be clustered by this function in a previous loop + if (edge !== undefined) { + var childNode = this.nodes[(edge.fromId == hubNode.id) ? edge.toId : edge.fromId]; + // we do not want hubs to merge with other hubs nor do we want to cluster itself. + if ((childNode.dynamicEdges.length <= (this.hubThreshold + absorptionSizeOffset)) && + (childNode.id != hubNode.id)) { + this._addToCluster(hubNode,childNode,force); + } + } + } + } + } + }, + + + + /** + * This function adds the child node to the parent node, creating a cluster if it is not already. + * + * @param {Node} parentNode | this is the node that will house the child node + * @param {Node} childNode | this node will be deleted from the global this.nodes and stored in the parent node + * @param {Boolean} force | true will only update the remainingEdges at the very end of the clustering, ensuring single level collapse + * @private + */ + _addToCluster : function(parentNode, childNode, force) { + // join child node in the parent node + parentNode.containedNodes[childNode.id] = childNode; + + // manage all the edges connected to the child and parent nodes + for (var i = 0; i < childNode.dynamicEdges.length; i++) { + var edge = childNode.dynamicEdges[i]; + if (edge.toId == parentNode.id || edge.fromId == parentNode.id) { // edge connected to parentNode + this._addToContainedEdges(parentNode,childNode,edge); + } + else { + this._connectEdgeToCluster(parentNode,childNode,edge); + } + } + // a contained node has no dynamic edges. + childNode.dynamicEdges = []; + + // remove circular edges from clusters + this._containCircularEdgesFromNode(parentNode,childNode); + + + // remove the childNode from the global nodes object + delete this.nodes[childNode.id]; + + // update the properties of the child and parent + var massBefore = parentNode.mass; + childNode.clusterSession = this.clusterSession; + parentNode.mass += childNode.mass; + parentNode.clusterSize += childNode.clusterSize; + parentNode.fontSize = Math.min(this.constants.clustering.maxFontSize, this.constants.nodes.fontSize + this.constants.clustering.fontSizeMultiplier*parentNode.clusterSize); + + // keep track of the clustersessions so we can open the cluster up as it has been formed. + if (parentNode.clusterSessions[parentNode.clusterSessions.length - 1] != this.clusterSession) { + parentNode.clusterSessions.push(this.clusterSession); + } + + // forced clusters only open from screen size and double tap + if (force == true) { + // parentNode.formationScale = Math.pow(1 - (1.0/11.0),this.clusterSession+3); + parentNode.formationScale = 0; + } + else { + parentNode.formationScale = this.scale; // The latest child has been added on this scale + } + + // recalculate the size of the node on the next time the node is rendered + parentNode.clearSizeCache(); + + // set the pop-out scale for the childnode + parentNode.containedNodes[childNode.id].formationScale = parentNode.formationScale; + + // nullify the movement velocity of the child, this is to avoid hectic behaviour + childNode.clearVelocity(); + + // the mass has altered, preservation of energy dictates the velocity to be updated + parentNode.updateVelocity(massBefore); + + // restart the simulation to reorganise all nodes + this.moving = true; + }, + + + /** + * This function will apply the changes made to the remainingEdges during the formation of the clusters. + * This is a seperate function to allow for level-wise collapsing of the node barnesHutTree. + * It has to be called if a level is collapsed. It is called by _formClusters(). + * @private + */ + _updateDynamicEdges : function() { + for (var i = 0; i < this.nodeIndices.length; i++) { + var node = this.nodes[this.nodeIndices[i]]; + node.dynamicEdgesLength = node.dynamicEdges.length; + + // this corrects for multiple edges pointing at the same other node + var correction = 0; + if (node.dynamicEdgesLength > 1) { + for (var j = 0; j < node.dynamicEdgesLength - 1; j++) { + var edgeToId = node.dynamicEdges[j].toId; + var edgeFromId = node.dynamicEdges[j].fromId; + for (var k = j+1; k < node.dynamicEdgesLength; k++) { + if ((node.dynamicEdges[k].toId == edgeToId && node.dynamicEdges[k].fromId == edgeFromId) || + (node.dynamicEdges[k].fromId == edgeToId && node.dynamicEdges[k].toId == edgeFromId)) { + correction += 1; + } + } + } + } + node.dynamicEdgesLength -= correction; + } + }, + + + /** + * This adds an edge from the childNode to the contained edges of the parent node + * + * @param parentNode | Node object + * @param childNode | Node object + * @param edge | Edge object + * @private + */ + _addToContainedEdges : function(parentNode, childNode, edge) { + // create an array object if it does not yet exist for this childNode + if (!(parentNode.containedEdges.hasOwnProperty(childNode.id))) { + parentNode.containedEdges[childNode.id] = [] + } + // add this edge to the list + parentNode.containedEdges[childNode.id].push(edge); + + // remove the edge from the global edges object + delete this.edges[edge.id]; + + // remove the edge from the parent object + for (var i = 0; i < parentNode.dynamicEdges.length; i++) { + if (parentNode.dynamicEdges[i].id == edge.id) { + parentNode.dynamicEdges.splice(i,1); + break; + } + } + }, + + /** + * This function connects an edge that was connected to a child node to the parent node. + * It keeps track of which nodes it has been connected to with the originalId array. + * + * @param {Node} parentNode | Node object + * @param {Node} childNode | Node object + * @param {Edge} edge | Edge object + * @private + */ + _connectEdgeToCluster : function(parentNode, childNode, edge) { + // handle circular edges + if (edge.toId == edge.fromId) { + this._addToContainedEdges(parentNode, childNode, edge); + } + else { + if (edge.toId == childNode.id) { // edge connected to other node on the "to" side + edge.originalToId.push(childNode.id); + edge.to = parentNode; + edge.toId = parentNode.id; + } + else { // edge connected to other node with the "from" side + + edge.originalFromId.push(childNode.id); + edge.from = parentNode; + edge.fromId = parentNode.id; + } + + this._addToReroutedEdges(parentNode,childNode,edge); + } + }, + + + /** + * If a node is connected to itself, a circular edge is drawn. When clustering we want to contain + * these edges inside of the cluster. + * + * @param parentNode + * @param childNode + * @private + */ + _containCircularEdgesFromNode : function(parentNode, childNode) { + // manage all the edges connected to the child and parent nodes + for (var i = 0; i < parentNode.dynamicEdges.length; i++) { + var edge = parentNode.dynamicEdges[i]; + // handle circular edges + if (edge.toId == edge.fromId) { + this._addToContainedEdges(parentNode, childNode, edge); + } + } + }, + + + /** + * This adds an edge from the childNode to the rerouted edges of the parent node + * + * @param parentNode | Node object + * @param childNode | Node object + * @param edge | Edge object + * @private + */ + _addToReroutedEdges : function(parentNode, childNode, edge) { + // create an array object if it does not yet exist for this childNode + // we store the edge in the rerouted edges so we can restore it when the cluster pops open + if (!(parentNode.reroutedEdges.hasOwnProperty(childNode.id))) { + parentNode.reroutedEdges[childNode.id] = []; + } + parentNode.reroutedEdges[childNode.id].push(edge); + + // this edge becomes part of the dynamicEdges of the cluster node + parentNode.dynamicEdges.push(edge); + }, + + + + /** + * This function connects an edge that was connected to a cluster node back to the child node. + * + * @param parentNode | Node object + * @param childNode | Node object + * @private + */ + _connectEdgeBackToChild : function(parentNode, childNode) { + if (parentNode.reroutedEdges.hasOwnProperty(childNode.id)) { + for (var i = 0; i < parentNode.reroutedEdges[childNode.id].length; i++) { + var edge = parentNode.reroutedEdges[childNode.id][i]; + if (edge.originalFromId[edge.originalFromId.length-1] == childNode.id) { + edge.originalFromId.pop(); + edge.fromId = childNode.id; + edge.from = childNode; + } + else { + edge.originalToId.pop(); + edge.toId = childNode.id; + edge.to = childNode; + } + + // append this edge to the list of edges connecting to the childnode + childNode.dynamicEdges.push(edge); + + // remove the edge from the parent object + for (var j = 0; j < parentNode.dynamicEdges.length; j++) { + if (parentNode.dynamicEdges[j].id == edge.id) { + parentNode.dynamicEdges.splice(j,1); + break; + } + } + } + // remove the entry from the rerouted edges + delete parentNode.reroutedEdges[childNode.id]; + } + }, + + + /** + * When loops are clustered, an edge can be both in the rerouted array and the contained array. + * This function is called last to verify that all edges in dynamicEdges are in fact connected to the + * parentNode + * + * @param parentNode | Node object + * @private + */ + _validateEdges : function(parentNode) { + for (var i = 0; i < parentNode.dynamicEdges.length; i++) { + var edge = parentNode.dynamicEdges[i]; + if (parentNode.id != edge.toId && parentNode.id != edge.fromId) { + parentNode.dynamicEdges.splice(i,1); + } + } + }, + + + /** + * This function released the contained edges back into the global domain and puts them back into the + * dynamic edges of both parent and child. + * + * @param {Node} parentNode | + * @param {Node} childNode | + * @private + */ + _releaseContainedEdges : function(parentNode, childNode) { + for (var i = 0; i < parentNode.containedEdges[childNode.id].length; i++) { + var edge = parentNode.containedEdges[childNode.id][i]; + + // put the edge back in the global edges object + this.edges[edge.id] = edge; + + // put the edge back in the dynamic edges of the child and parent + childNode.dynamicEdges.push(edge); + parentNode.dynamicEdges.push(edge); + } + // remove the entry from the contained edges + delete parentNode.containedEdges[childNode.id]; + + }, + + + + + // ------------------- UTILITY FUNCTIONS ---------------------------- // + + + /** + * This updates the node labels for all nodes (for debugging purposes) + */ + updateLabels : function() { + var nodeId; + // update node labels + for (nodeId in this.nodes) { + if (this.nodes.hasOwnProperty(nodeId)) { + var node = this.nodes[nodeId]; + if (node.clusterSize > 1) { + node.label = "[".concat(String(node.clusterSize),"]"); + } + } + } + + // update node labels + for (nodeId in this.nodes) { + if (this.nodes.hasOwnProperty(nodeId)) { + node = this.nodes[nodeId]; + if (node.clusterSize == 1) { + if (node.originalLabel !== undefined) { + node.label = node.originalLabel; + } + else { + node.label = String(node.id); + } + } + } + } + +// /* Debug Override */ +// for (nodeId in this.nodes) { +// if (this.nodes.hasOwnProperty(nodeId)) { +// node = this.nodes[nodeId]; +// node.label = String(node.level); +// } +// } + + }, + + + /** + * We want to keep the cluster level distribution rather small. This means we do not want unclustered nodes + * if the rest of the nodes are already a few cluster levels in. + * To fix this we use this function. It determines the min and max cluster level and sends nodes that have not + * clustered enough to the clusterToSmallestNeighbours function. + */ + normalizeClusterLevels : function() { + var maxLevel = 0; + var minLevel = 1e9; + var clusterLevel = 0; + + // we loop over all nodes in the list + for (var nodeId in this.nodes) { + if (this.nodes.hasOwnProperty(nodeId)) { + clusterLevel = this.nodes[nodeId].clusterSessions.length; + if (maxLevel < clusterLevel) {maxLevel = clusterLevel;} + if (minLevel > clusterLevel) {minLevel = clusterLevel;} + } + } + + if (maxLevel - minLevel > this.constants.clustering.clusterLevelDifference) { + var amountOfNodes = this.nodeIndices.length; + var targetLevel = maxLevel - this.constants.clustering.clusterLevelDifference; + // we loop over all nodes in the list + for (var nodeId in this.nodes) { + if (this.nodes.hasOwnProperty(nodeId)) { + if (this.nodes[nodeId].clusterSessions.length < targetLevel) { + this._clusterToSmallestNeighbour(this.nodes[nodeId]); + } + } + } + this._updateNodeIndexList(); + this._updateDynamicEdges(); + // if a cluster was formed, we increase the clusterSession + if (this.nodeIndices.length != amountOfNodes) { + this.clusterSession += 1; + } + } + }, + + + + /** + * This function determines if the cluster we want to decluster is in the active area + * this means around the zoom center + * + * @param {Node} node + * @returns {boolean} + * @private + */ + _nodeInActiveArea : function(node) { + return ( + Math.abs(node.x - this.areaCenter.x) <= this.constants.clustering.activeAreaBoxSize/this.scale + && + Math.abs(node.y - this.areaCenter.y) <= this.constants.clustering.activeAreaBoxSize/this.scale + ) + }, + + + /** + * This is an adaptation of the original repositioning function. This is called if the system is clustered initially + * It puts large clusters away from the center and randomizes the order. + * + */ + repositionNodes : function() { + for (var i = 0; i < this.nodeIndices.length; i++) { + var node = this.nodes[this.nodeIndices[i]]; + if ((node.xFixed == false || node.yFixed == false)) { + var radius = 10 * 0.1*this.nodeIndices.length * Math.min(100,node.mass); + var angle = 2 * Math.PI * Math.random(); + if (node.xFixed == false) {node.x = radius * Math.cos(angle);} + if (node.yFixed == false) {node.y = radius * Math.sin(angle);} + this._repositionBezierNodes(node); + } + } + }, + + + /** + * We determine how many connections denote an important hub. + * We take the mean + 2*std as the important hub size. (Assuming a normal distribution of data, ~2.2%) + * + * @private + */ + _getHubSize : function() { + var average = 0; + var averageSquared = 0; + var hubCounter = 0; + var largestHub = 0; + + for (var i = 0; i < this.nodeIndices.length; i++) { + + var node = this.nodes[this.nodeIndices[i]]; + if (node.dynamicEdgesLength > largestHub) { + largestHub = node.dynamicEdgesLength; + } + average += node.dynamicEdgesLength; + averageSquared += Math.pow(node.dynamicEdgesLength,2); + hubCounter += 1; + } + average = average / hubCounter; + averageSquared = averageSquared / hubCounter; + + var variance = averageSquared - Math.pow(average,2); + + var standardDeviation = Math.sqrt(variance); + + this.hubThreshold = Math.floor(average + 2*standardDeviation); + + // always have at least one to cluster + if (this.hubThreshold > largestHub) { + this.hubThreshold = largestHub; + } + + // console.log("average",average,"averageSQ",averageSquared,"var",variance,"std",standardDeviation); + // console.log("hubThreshold:",this.hubThreshold); + }, + + + /** + * We reduce the amount of "extension nodes" or chains. These are not quickly clustered with the outliers and hubs methods + * with this amount we can cluster specifically on these chains. + * + * @param {Number} fraction | between 0 and 1, the percentage of chains to reduce + * @private + */ + _reduceAmountOfChains : function(fraction) { + this.hubThreshold = 2; + var reduceAmount = Math.floor(this.nodeIndices.length * fraction); + for (var nodeId in this.nodes) { + if (this.nodes.hasOwnProperty(nodeId)) { + if (this.nodes[nodeId].dynamicEdgesLength == 2 && this.nodes[nodeId].dynamicEdges.length >= 2) { + if (reduceAmount > 0) { + this._formClusterFromHub(this.nodes[nodeId],true,true,1); + reduceAmount -= 1; + } + } + } + } + }, + + /** + * We get the amount of "extension nodes" or chains. These are not quickly clustered with the outliers and hubs methods + * with this amount we can cluster specifically on these chains. + * + * @private + */ + _getChainFraction : function() { + var chains = 0; + var total = 0; + for (var nodeId in this.nodes) { + if (this.nodes.hasOwnProperty(nodeId)) { + if (this.nodes[nodeId].dynamicEdgesLength == 2 && this.nodes[nodeId].dynamicEdges.length >= 2) { + chains += 1; + } + total += 1; + } + } + return chains/total; + } + +}; + + +var SelectionMixin = { + + /** + * This function can be called from the _doInAllSectors function + * + * @param object + * @param overlappingNodes + * @private + */ + _getNodesOverlappingWith : function(object, overlappingNodes) { + var nodes = this.nodes; + for (var nodeId in nodes) { + if (nodes.hasOwnProperty(nodeId)) { + if (nodes[nodeId].isOverlappingWith(object)) { + overlappingNodes.push(nodeId); + } + } + } + }, + + /** + * retrieve all nodes overlapping with given object + * @param {Object} object An object with parameters left, top, right, bottom + * @return {Number[]} An array with id's of the overlapping nodes + * @private + */ + _getAllNodesOverlappingWith : function (object) { + var overlappingNodes = []; + this._doInAllActiveSectors("_getNodesOverlappingWith",object,overlappingNodes); + return overlappingNodes; + }, + + + /** + * Return a position object in canvasspace from a single point in screenspace + * + * @param pointer + * @returns {{left: number, top: number, right: number, bottom: number}} + * @private + */ + _pointerToPositionObject : function(pointer) { + var x = this._canvasToX(pointer.x); + var y = this._canvasToY(pointer.y); + + return {left: x, + top: y, + right: x, + bottom: y}; + }, + + + /** + * Get the top node at the a specific point (like a click) + * + * @param {{x: Number, y: Number}} pointer + * @return {Node | null} node + * @private + */ + _getNodeAt : function (pointer) { + // we first check if this is an navigation controls element + var positionObject = this._pointerToPositionObject(pointer); + var overlappingNodes = this._getAllNodesOverlappingWith(positionObject); + + // if there are overlapping nodes, select the last one, this is the + // one which is drawn on top of the others + if (overlappingNodes.length > 0) { + return this.nodes[overlappingNodes[overlappingNodes.length - 1]]; + } + else { + return null; + } + }, + + + /** + * retrieve all edges overlapping with given object, selector is around center + * @param {Object} object An object with parameters left, top, right, bottom + * @return {Number[]} An array with id's of the overlapping nodes + * @private + */ + _getEdgesOverlappingWith : function (object, overlappingEdges) { + var edges = this.edges; + for (var edgeId in edges) { + if (edges.hasOwnProperty(edgeId)) { + if (edges[edgeId].isOverlappingWith(object)) { + overlappingEdges.push(edgeId); + } + } + } + }, + + + /** + * retrieve all nodes overlapping with given object + * @param {Object} object An object with parameters left, top, right, bottom + * @return {Number[]} An array with id's of the overlapping nodes + * @private + */ + _getAllEdgesOverlappingWith : function (object) { + var overlappingEdges = []; + this._doInAllActiveSectors("_getEdgesOverlappingWith",object,overlappingEdges); + return overlappingEdges; + }, + + /** + * Place holder. To implement change the _getNodeAt to a _getObjectAt. Have the _getObjectAt call + * _getNodeAt and _getEdgesAt, then priortize the selection to user preferences. + * + * @param pointer + * @returns {null} + * @private + */ + _getEdgeAt : function(pointer) { + var positionObject = this._pointerToPositionObject(pointer); + var overlappingEdges = this._getAllEdgesOverlappingWith(positionObject); + + if (overlappingEdges.length > 0) { + return this.edges[overlappingEdges[overlappingEdges.length - 1]]; + } + else { + return null; + } + }, + + + /** + * Add object to the selection array. + * + * @param obj + * @private + */ + _addToSelection : function(obj) { + if (obj instanceof Node) { + this.selectionObj.nodes[obj.id] = obj; + } + else { + this.selectionObj.edges[obj.id] = obj; + } + + }, + + + /** + * Remove a single option from selection. + * + * @param {Object} obj + * @private + */ + _removeFromSelection : function(obj) { + if (obj instanceof Node) { + delete this.selectionObj.nodes[obj.id]; + } + else { + delete this.selectionObj.edges[obj.id]; + } + }, + + + /** + * Unselect all. The selectionObj is useful for this. + * + * @param {Boolean} [doNotTrigger] | ignore trigger + * @private + */ + _unselectAll : function(doNotTrigger) { + if (doNotTrigger === undefined) { + doNotTrigger = false; + } + for(var nodeId in this.selectionObj.nodes) { + if(this.selectionObj.nodes.hasOwnProperty(nodeId)) { + this.selectionObj.nodes[nodeId].unselect(); + } + } + for(var edgeId in this.selectionObj.edges) { + if(this.selectionObj.edges.hasOwnProperty(edgeId)) { + this.selectionObj.edges[edgeId].unselect();; + } + } + + this.selectionObj = {nodes:{},edges:{}}; + + if (doNotTrigger == false) { + this.emit('select', this.getSelection()); + } + }, + + /** + * Unselect all clusters. The selectionObj is useful for this. + * + * @param {Boolean} [doNotTrigger] | ignore trigger + * @private + */ + _unselectClusters : function(doNotTrigger) { + if (doNotTrigger === undefined) { + doNotTrigger = false; + } + + for (var nodeId in this.selectionObj.nodes) { + if (this.selectionObj.nodes.hasOwnProperty(nodeId)) { + if (this.selectionObj.nodes[nodeId].clusterSize > 1) { + this.selectionObj.nodes[nodeId].unselect(); + this._removeFromSelection(this.selectionObj.nodes[nodeId]); + } + } + } + + if (doNotTrigger == false) { + this.emit('select', this.getSelection()); + } + }, + + + /** + * return the number of selected nodes + * + * @returns {number} + * @private + */ + _getSelectedNodeCount : function() { + var count = 0; + for (var nodeId in this.selectionObj.nodes) { + if (this.selectionObj.nodes.hasOwnProperty(nodeId)) { + count += 1; + } + } + return count; + }, + + /** + * return the number of selected nodes + * + * @returns {number} + * @private + */ + _getSelectedNode : function() { + for (var nodeId in this.selectionObj.nodes) { + if (this.selectionObj.nodes.hasOwnProperty(nodeId)) { + return this.selectionObj.nodes[nodeId]; + } + } + return null; + }, + + + /** + * return the number of selected edges + * + * @returns {number} + * @private + */ + _getSelectedEdgeCount : function() { + var count = 0; + for (var edgeId in this.selectionObj.edges) { + if (this.selectionObj.edges.hasOwnProperty(edgeId)) { + count += 1; + } + } + return count; + }, + + + /** + * return the number of selected objects. + * + * @returns {number} + * @private + */ + _getSelectedObjectCount : function() { + var count = 0; + for(var nodeId in this.selectionObj.nodes) { + if(this.selectionObj.nodes.hasOwnProperty(nodeId)) { + count += 1; + } + } + for(var edgeId in this.selectionObj.edges) { + if(this.selectionObj.edges.hasOwnProperty(edgeId)) { + count += 1; + } + } + return count; + }, + + /** + * Check if anything is selected + * + * @returns {boolean} + * @private + */ + _selectionIsEmpty : function() { + for(var nodeId in this.selectionObj.nodes) { + if(this.selectionObj.nodes.hasOwnProperty(nodeId)) { + return false; + } + } + for(var edgeId in this.selectionObj.edges) { + if(this.selectionObj.edges.hasOwnProperty(edgeId)) { + return false; + } + } + return true; + }, + + + /** + * check if one of the selected nodes is a cluster. + * + * @returns {boolean} + * @private + */ + _clusterInSelection : function() { + for(var nodeId in this.selectionObj.nodes) { + if(this.selectionObj.nodes.hasOwnProperty(nodeId)) { + if (this.selectionObj.nodes[nodeId].clusterSize > 1) { + return true; + } + } + } + return false; + }, + + /** + * select the edges connected to the node that is being selected + * + * @param {Node} node + * @private + */ + _selectConnectedEdges : function(node) { + for (var i = 0; i < node.dynamicEdges.length; i++) { + var edge = node.dynamicEdges[i]; + edge.select(); + this._addToSelection(edge); + } + }, + + + /** + * unselect the edges connected to the node that is being selected + * + * @param {Node} node + * @private + */ + _unselectConnectedEdges : function(node) { + for (var i = 0; i < node.dynamicEdges.length; i++) { + var edge = node.dynamicEdges[i]; + edge.unselect(); + this._removeFromSelection(edge); + } + }, + + + + /** + * This is called when someone clicks on a node. either select or deselect it. + * If there is an existing selection and we don't want to append to it, clear the existing selection + * + * @param {Node || Edge} object + * @param {Boolean} append + * @param {Boolean} [doNotTrigger] | ignore trigger + * @private + */ + _selectObject : function(object, append, doNotTrigger) { + if (doNotTrigger === undefined) { + doNotTrigger = false; + } + + if (this._selectionIsEmpty() == false && append == false && this.forceAppendSelection == false) { + this._unselectAll(true); + } + + if (object.selected == false) { + object.select(); + this._addToSelection(object); + if (object instanceof Node && this.blockConnectingEdgeSelection == false) { + this._selectConnectedEdges(object); + } + } + else { + object.unselect(); + this._removeFromSelection(object); + } + if (doNotTrigger == false) { + this.emit('select', this.getSelection()); + } + }, + + + /** + * handles the selection part of the touch, only for navigation controls elements; + * Touch is triggered before tap, also before hold. Hold triggers after a while. + * This is the most responsive solution + * + * @param {Object} pointer + * @private + */ + _handleTouch : function(pointer) { + + }, + + + /** + * handles the selection part of the tap; + * + * @param {Object} pointer + * @private + */ + _handleTap : function(pointer) { + var node = this._getNodeAt(pointer); + if (node != null) { + this._selectObject(node,false); + } + else { + var edge = this._getEdgeAt(pointer); + if (edge != null) { + this._selectObject(edge,false); + } + else { + this._unselectAll(); + } + } + this.emit("click", this.getSelection()); + this._redraw(); + }, + + + /** + * handles the selection part of the double tap and opens a cluster if needed + * + * @param {Object} pointer + * @private + */ + _handleDoubleTap : function(pointer) { + var node = this._getNodeAt(pointer); + if (node != null && node !== undefined) { + // we reset the areaCenter here so the opening of the node will occur + this.areaCenter = {"x" : this._canvasToX(pointer.x), + "y" : this._canvasToY(pointer.y)}; + this.openCluster(node); + } + this.emit("doubleClick", this.getSelection()); + }, + + + /** + * Handle the onHold selection part + * + * @param pointer + * @private + */ + _handleOnHold : function(pointer) { + var node = this._getNodeAt(pointer); + if (node != null) { + this._selectObject(node,true); + } + else { + var edge = this._getEdgeAt(pointer); + if (edge != null) { + this._selectObject(edge,true); + } + } + this._redraw(); + }, + + + /** + * handle the onRelease event. These functions are here for the navigation controls module. + * + * @private + */ + _handleOnRelease : function(pointer) { + + }, + + + + /** + * + * retrieve the currently selected objects + * @return {Number[] | String[]} selection An array with the ids of the + * selected nodes. + */ + getSelection : function() { + var nodeIds = this.getSelectedNodes(); + var edgeIds = this.getSelectedEdges(); + return {nodes:nodeIds, edges:edgeIds}; + }, + + /** + * + * retrieve the currently selected nodes + * @return {String} selection An array with the ids of the + * selected nodes. + */ + getSelectedNodes : function() { + var idArray = []; + for(var nodeId in this.selectionObj.nodes) { + if(this.selectionObj.nodes.hasOwnProperty(nodeId)) { + idArray.push(nodeId); + } + } + return idArray + }, + + /** + * + * retrieve the currently selected edges + * @return {Array} selection An array with the ids of the + * selected nodes. + */ + getSelectedEdges : function() { + var idArray = []; + for(var edgeId in this.selectionObj.edges) { + if(this.selectionObj.edges.hasOwnProperty(edgeId)) { + idArray.push(edgeId); + } + } + return idArray; + }, + + + /** + * select zero or more nodes + * @param {Number[] | String[]} selection An array with the ids of the + * selected nodes. + */ + setSelection : function(selection) { + var i, iMax, id; + + if (!selection || (selection.length == undefined)) + throw 'Selection must be an array with ids'; + + // first unselect any selected node + this._unselectAll(true); + + for (i = 0, iMax = selection.length; i < iMax; i++) { + id = selection[i]; + + var node = this.nodes[id]; + if (!node) { + throw new RangeError('Node with id "' + id + '" not found'); + } + this._selectObject(node,true,true); + } + this.redraw(); + }, + + + /** + * Validate the selection: remove ids of nodes which no longer exist + * @private + */ + _updateSelection : function () { + for(var nodeId in this.selectionObj.nodes) { + if(this.selectionObj.nodes.hasOwnProperty(nodeId)) { + if (!this.nodes.hasOwnProperty(nodeId)) { + delete this.selectionObj.nodes[nodeId]; + } + } + } + for(var edgeId in this.selectionObj.edges) { + if(this.selectionObj.edges.hasOwnProperty(edgeId)) { + if (!this.edges.hasOwnProperty(edgeId)) { + delete this.selectionObj.edges[edgeId]; + } + } + } + } +}; + + + +/** + * Created by Alex on 1/22/14. + */ + +var NavigationMixin = { + + _cleanNavigation : function() { + // clean up previosu navigation items + var wrapper = document.getElementById('graph-navigation_wrapper'); + if (wrapper != null) { + this.containerElement.removeChild(wrapper); + } + document.onmouseup = null; + }, + + /** + * Creation of the navigation controls nodes. They are drawn over the rest of the nodes and are not affected by scale and translation + * they have a triggerFunction which is called on click. If the position of the navigation controls is dependent + * on this.frame.canvas.clientWidth or this.frame.canvas.clientHeight, we flag horizontalAlignLeft and verticalAlignTop false. + * This means that the location will be corrected by the _relocateNavigation function on a size change of the canvas. + * + * @private + */ + _loadNavigationElements : function() { + this._cleanNavigation(); + + this.navigationDivs = {}; + var navigationDivs = ['up','down','left','right','zoomIn','zoomOut','zoomExtends']; + var navigationDivActions = ['_moveUp','_moveDown','_moveLeft','_moveRight','_zoomIn','_zoomOut','zoomExtent']; + + this.navigationDivs['wrapper'] = document.createElement('div'); + this.navigationDivs['wrapper'].id = "graph-navigation_wrapper"; + this.navigationDivs['wrapper'].style.position = "absolute"; + this.navigationDivs['wrapper'].style.width = this.frame.canvas.clientWidth + "px"; + this.navigationDivs['wrapper'].style.height = this.frame.canvas.clientHeight + "px"; + this.containerElement.insertBefore(this.navigationDivs['wrapper'],this.frame); + + for (var i = 0; i < navigationDivs.length; i++) { + this.navigationDivs[navigationDivs[i]] = document.createElement('div'); + this.navigationDivs[navigationDivs[i]].id = "graph-navigation_" + navigationDivs[i]; + this.navigationDivs[navigationDivs[i]].className = "graph-navigation " + navigationDivs[i]; + this.navigationDivs['wrapper'].appendChild(this.navigationDivs[navigationDivs[i]]); + this.navigationDivs[navigationDivs[i]].onmousedown = this[navigationDivActions[i]].bind(this); + } + + document.onmouseup = this._stopMovement.bind(this); + }, + + /** + * this stops all movement induced by the navigation buttons + * + * @private + */ + _stopMovement : function() { + this._xStopMoving(); + this._yStopMoving(); + this._stopZoom(); + }, + + + /** + * stops the actions performed by page up and down etc. + * + * @param event + * @private + */ + _preventDefault : function(event) { + if (event !== undefined) { + if (event.preventDefault) { + event.preventDefault(); + } else { + event.returnValue = false; + } + } + }, + + + /** + * move the screen up + * By using the increments, instead of adding a fixed number to the translation, we keep fluent and + * instant movement. The onKeypress event triggers immediately, then pauses, then triggers frequently + * To avoid this behaviour, we do the translation in the start loop. + * + * @private + */ + _moveUp : function(event) { + this.yIncrement = this.constants.keyboard.speed.y; + this.start(); // if there is no node movement, the calculation wont be done + this._preventDefault(event); + if (this.navigationDivs) { + this.navigationDivs['up'].className += " active"; + } + }, + + + /** + * move the screen down + * @private + */ + _moveDown : function(event) { + this.yIncrement = -this.constants.keyboard.speed.y; + this.start(); // if there is no node movement, the calculation wont be done + this._preventDefault(event); + if (this.navigationDivs) { + this.navigationDivs['down'].className += " active"; + } + }, + + + /** + * move the screen left + * @private + */ + _moveLeft : function(event) { + this.xIncrement = this.constants.keyboard.speed.x; + this.start(); // if there is no node movement, the calculation wont be done + this._preventDefault(event); + if (this.navigationDivs) { + this.navigationDivs['left'].className += " active"; + } + }, + + + /** + * move the screen right + * @private + */ + _moveRight : function(event) { + this.xIncrement = -this.constants.keyboard.speed.y; + this.start(); // if there is no node movement, the calculation wont be done + this._preventDefault(event); + if (this.navigationDivs) { + this.navigationDivs['right'].className += " active"; + } + }, + + + /** + * Zoom in, using the same method as the movement. + * @private + */ + _zoomIn : function(event) { + this.zoomIncrement = this.constants.keyboard.speed.zoom; + this.start(); // if there is no node movement, the calculation wont be done + this._preventDefault(event); + if (this.navigationDivs) { + this.navigationDivs['zoomIn'].className += " active"; + } + }, + + + /** + * Zoom out + * @private + */ + _zoomOut : function() { + this.zoomIncrement = -this.constants.keyboard.speed.zoom; + this.start(); // if there is no node movement, the calculation wont be done + this._preventDefault(event); + if (this.navigationDivs) { + this.navigationDivs['zoomOut'].className += " active"; + } + }, + + + /** + * Stop zooming and unhighlight the zoom controls + * @private + */ + _stopZoom : function() { + this.zoomIncrement = 0; + if (this.navigationDivs) { + this.navigationDivs['zoomIn'].className = this.navigationDivs['zoomIn'].className.replace(" active",""); + this.navigationDivs['zoomOut'].className = this.navigationDivs['zoomOut'].className.replace(" active",""); + } + }, + + + /** + * Stop moving in the Y direction and unHighlight the up and down + * @private + */ + _yStopMoving : function() { + this.yIncrement = 0; + if (this.navigationDivs) { + this.navigationDivs['up'].className = this.navigationDivs['up'].className.replace(" active",""); + this.navigationDivs['down'].className = this.navigationDivs['down'].className.replace(" active",""); + } + }, + + + /** + * Stop moving in the X direction and unHighlight left and right. + * @private + */ + _xStopMoving : function() { + this.xIncrement = 0; + if (this.navigationDivs) { + this.navigationDivs['left'].className = this.navigationDivs['left'].className.replace(" active",""); + this.navigationDivs['right'].className = this.navigationDivs['right'].className.replace(" active",""); + } + } + + +}; + +/** + * Created by Alex on 2/10/14. + */ + + +var graphMixinLoaders = { + + /** + * Load a mixin into the graph object + * + * @param {Object} sourceVariable | this object has to contain functions. + * @private + */ + _loadMixin : function(sourceVariable) { + for (var mixinFunction in sourceVariable) { + if (sourceVariable.hasOwnProperty(mixinFunction)) { + Graph.prototype[mixinFunction] = sourceVariable[mixinFunction]; + } + } + }, + + + /** + * removes a mixin from the graph object. + * + * @param {Object} sourceVariable | this object has to contain functions. + * @private + */ + _clearMixin : function(sourceVariable) { + for (var mixinFunction in sourceVariable) { + if (sourceVariable.hasOwnProperty(mixinFunction)) { + Graph.prototype[mixinFunction] = undefined; + } + } + }, + + + /** + * Mixin the physics system and initialize the parameters required. + * + * @private + */ + _loadPhysicsSystem : function() { + this._loadMixin(physicsMixin); + this._loadSelectedForceSolver(); + if (this.constants.configurePhysics == true) { + this._loadPhysicsConfiguration(); + } + }, + + + + + /** + * Mixin the cluster system and initialize the parameters required. + * + * @private + */ + _loadClusterSystem : function() { + this.clusterSession = 0; + this.hubThreshold = 5; + this._loadMixin(ClusterMixin); + }, + + + /** + * Mixin the sector system and initialize the parameters required + * + * @private + */ + _loadSectorSystem : function() { + this.sectors = { }, + this.activeSector = ["default"]; + this.sectors["active"] = { }, + this.sectors["active"]["default"] = {"nodes":{}, + "edges":{}, + "nodeIndices":[], + "formationScale": 1.0, + "drawingNode": undefined }; + this.sectors["frozen"] = {}, + this.sectors["support"] = {"nodes":{}, + "edges":{}, + "nodeIndices":[], + "formationScale": 1.0, + "drawingNode": undefined }; + + this.nodeIndices = this.sectors["active"]["default"]["nodeIndices"]; // the node indices list is used to speed up the computation of the repulsion fields + + this._loadMixin(SectorMixin); + }, + + + /** + * Mixin the selection system and initialize the parameters required + * + * @private + */ + _loadSelectionSystem : function() { + this.selectionObj = {nodes:{},edges:{}}; + + this._loadMixin(SelectionMixin); + }, + + + /** + * Mixin the navigationUI (User Interface) system and initialize the parameters required + * + * @private + */ + _loadManipulationSystem : function() { + // reset global variables -- these are used by the selection of nodes and edges. + this.blockConnectingEdgeSelection = false; + this.forceAppendSelection = false + + if (this.constants.dataManipulation.enabled == true) { + // load the manipulator HTML elements. All styling done in css. + if (this.manipulationDiv === undefined) { + this.manipulationDiv = document.createElement('div'); + this.manipulationDiv.className = 'graph-manipulationDiv'; + this.manipulationDiv.id = 'graph-manipulationDiv'; + if (this.editMode == true) { + this.manipulationDiv.style.display = "block"; + } + else { + this.manipulationDiv.style.display = "none"; + } + this.containerElement.insertBefore(this.manipulationDiv, this.frame); + } + + if (this.editModeDiv === undefined) { + this.editModeDiv = document.createElement('div'); + this.editModeDiv.className = 'graph-manipulation-editMode'; + this.editModeDiv.id = 'graph-manipulation-editMode'; + if (this.editMode == true) { + this.editModeDiv.style.display = "none"; + } + else { + this.editModeDiv.style.display = "block"; + } + this.containerElement.insertBefore(this.editModeDiv, this.frame); + } + + if (this.closeDiv === undefined) { + this.closeDiv = document.createElement('div'); + this.closeDiv.className = 'graph-manipulation-closeDiv'; + this.closeDiv.id = 'graph-manipulation-closeDiv'; + this.closeDiv.style.display = this.manipulationDiv.style.display; + this.containerElement.insertBefore(this.closeDiv, this.frame); + } + + // load the manipulation functions + this._loadMixin(manipulationMixin); + + // create the manipulator toolbar + this._createManipulatorBar(); + } + else { + if (this.manipulationDiv !== undefined) { + // removes all the bindings and overloads + this._createManipulatorBar(); + // remove the manipulation divs + this.containerElement.removeChild(this.manipulationDiv); + this.containerElement.removeChild(this.editModeDiv); + this.containerElement.removeChild(this.closeDiv); + + this.manipulationDiv = undefined; + this.editModeDiv = undefined; + this.closeDiv = undefined; + // remove the mixin functions + this._clearMixin(manipulationMixin); + } + } + }, + + + /** + * Mixin the navigation (User Interface) system and initialize the parameters required + * + * @private + */ + _loadNavigationControls : function() { + this._loadMixin(NavigationMixin); + + // the clean function removes the button divs, this is done to remove the bindings. + this._cleanNavigation(); + if (this.constants.navigation.enabled == true) { + this._loadNavigationElements(); + } + }, + + + /** + * Mixin the hierarchical layout system. + * + * @private + */ + _loadHierarchySystem : function() { + this._loadMixin(HierarchicalLayoutMixin); + } + +} + +/** + * @constructor Graph + * Create a graph visualization, displaying nodes and edges. + * + * @param {Element} container The DOM element in which the Graph will + * be created. Normally a div element. + * @param {Object} data An object containing parameters + * {Array} nodes + * {Array} edges + * @param {Object} options Options + */ +function Graph (container, data, options) { + + this._initializeMixinLoaders(); + + // create variables and set default values + this.containerElement = container; + this.width = '100%'; + this.height = '100%'; + + // render and calculation settings + this.renderRefreshRate = 60; // hz (fps) + this.renderTimestep = 1000 / this.renderRefreshRate; // ms -- saves calculation later on + this.renderTime = 0.5 * this.renderTimestep; // measured time it takes to render a frame + this.maxPhysicsTicksPerRender = 3; // max amount of physics ticks per render step. + this.physicsDiscreteStepsize = 0.65; // discrete stepsize of the simulation + + this.stabilize = true; // stabilize before displaying the graph + this.selectable = true; + + // these functions are triggered when the dataset is edited + this.triggerFunctions = {add:null,edit:null,connect:null,delete:null}; + + // set constant values + this.constants = { + nodes: { + radiusMin: 5, + radiusMax: 20, + radius: 5, + shape: 'ellipse', + image: undefined, + widthMin: 16, // px + widthMax: 64, // px + fixed: false, + fontColor: 'black', + fontSize: 14, // px + fontFace: 'verdana', + level: -1, + color: { + border: '#2B7CE9', + background: '#97C2FC', + highlight: { + border: '#2B7CE9', + background: '#D2E5FF' + } + }, + borderColor: '#2B7CE9', + backgroundColor: '#97C2FC', + highlightColor: '#D2E5FF', + group: undefined + }, + edges: { + widthMin: 1, + widthMax: 15, + width: 1, + style: 'line', + color: { + color:'#848484', + highlight:'#848484' + }, + fontColor: '#343434', + fontSize: 14, // px + fontFace: 'arial', + dash: { + length: 10, + gap: 5, + altLength: undefined + } + }, + configurePhysics:false, + physics: { + barnesHut: { + enabled: true, + theta: 1 / 0.6, // inverted to save time during calculation + gravitationalConstant: -2000, + centralGravity: 0.3, + springLength: 95, + springConstant: 0.04, + damping: 0.09 + }, + repulsion: { + centralGravity: 0.1, + springLength: 200, + springConstant: 0.05, + nodeDistance: 100, + damping: 0.09 + }, + hierarchicalRepulsion: { + enabled: false, + centralGravity: 0.0, + springLength: 100, + springConstant: 0.01, + nodeDistance: 60, + damping: 0.09 + }, + damping: null, + centralGravity: null, + springLength: null, + springConstant: null + }, + clustering: { // Per Node in Cluster = PNiC + enabled: false, // (Boolean) | global on/off switch for clustering. + initialMaxNodes: 100, // (# nodes) | if the initial amount of nodes is larger than this, we cluster until the total number is less than this threshold. + clusterThreshold:500, // (# nodes) | during calculate forces, we check if the total number of nodes is larger than this. If it is, cluster until reduced to reduceToNodes + reduceToNodes:300, // (# nodes) | during calculate forces, we check if the total number of nodes is larger than clusterThreshold. If it is, cluster until reduced to this + chainThreshold: 0.4, // (% of all drawn nodes)| maximum percentage of allowed chainnodes (long strings of connected nodes) within all nodes. (lower means less chains). + clusterEdgeThreshold: 20, // (px) | edge length threshold. if smaller, this node is clustered. + sectorThreshold: 100, // (# nodes in cluster) | cluster size threshold. If larger, expanding in own sector. + screenSizeThreshold: 0.2, // (% of canvas) | relative size threshold. If the width or height of a clusternode takes up this much of the screen, decluster node. + fontSizeMultiplier: 4.0, // (px PNiC) | how much the cluster font size grows per node in cluster (in px). + maxFontSize: 1000, + forceAmplification: 0.1, // (multiplier PNiC) | factor of increase fo the repulsion force of a cluster (per node in cluster). + distanceAmplification: 0.1, // (multiplier PNiC) | factor how much the repulsion distance of a cluster increases (per node in cluster). + edgeGrowth: 20, // (px PNiC) | amount of clusterSize connected to the edge is multiplied with this and added to edgeLength. + nodeScaling: {width: 1, // (px PNiC) | growth of the width per node in cluster. + height: 1, // (px PNiC) | growth of the height per node in cluster. + radius: 1}, // (px PNiC) | growth of the radius per node in cluster. + maxNodeSizeIncrements: 600, // (# increments) | max growth of the width per node in cluster. + activeAreaBoxSize: 80, // (px) | box area around the curser where clusters are popped open. + clusterLevelDifference: 2 + }, + navigation: { + enabled: false + }, + keyboard: { + enabled: false, + speed: {x: 10, y: 10, zoom: 0.02} + }, + dataManipulation: { + enabled: false, + initiallyVisible: false + }, + hierarchicalLayout: { + enabled:false, + levelSeparation: 150, + nodeSpacing: 100, + direction: "UD" // UD, DU, LR, RL + }, + freezeForStabilization: false, + smoothCurves: true, + maxVelocity: 10, + minVelocity: 0.1, // px/s + stabilizationIterations: 1000 // maximum number of iteration to stabilize + }; + this.editMode = this.constants.dataManipulation.initiallyVisible; + + // Node variables + var graph = this; + this.groups = new Groups(); // object with groups + this.images = new Images(); // object with images + this.images.setOnloadCallback(function () { + graph._redraw(); + }); + + // keyboard navigation variables + this.xIncrement = 0; + this.yIncrement = 0; + this.zoomIncrement = 0; + + // loading all the mixins: + // load the force calculation functions, grouped under the physics system. + this._loadPhysicsSystem(); + // create a frame and canvas + this._create(); + // load the sector system. (mandatory, fully integrated with Graph) + this._loadSectorSystem(); + // load the cluster system. (mandatory, even when not using the cluster system, there are function calls to it) + this._loadClusterSystem(); + // load the selection system. (mandatory, required by Graph) + this._loadSelectionSystem(); + // load the selection system. (mandatory, required by Graph) + this._loadHierarchySystem(); + + // apply options + this.setOptions(options); + + // other vars + this.freezeSimulation = false;// freeze the simulation + this.cachedFunctions = {}; + + // containers for nodes and edges + this.calculationNodes = {}; + this.calculationNodeIndices = []; + this.nodeIndices = []; // array with all the indices of the nodes. Used to speed up forces calculation + this.nodes = {}; // object with Node objects + this.edges = {}; // object with Edge objects + + // position and scale variables and objects + this.canvasTopLeft = {"x": 0,"y": 0}; // coordinates of the top left of the canvas. they will be set during _redraw. + this.canvasBottomRight = {"x": 0,"y": 0}; // coordinates of the bottom right of the canvas. they will be set during _redraw + this.pointerPosition = {"x": 0,"y": 0}; // coordinates of the bottom right of the canvas. they will be set during _redraw + this.areaCenter = {}; // object with x and y elements used for determining the center of the zoom action + this.scale = 1; // defining the global scale variable in the constructor + this.previousScale = this.scale; // this is used to check if the zoom operation is zooming in or out + + // datasets or dataviews + this.nodesData = null; // A DataSet or DataView + this.edgesData = null; // A DataSet or DataView + + // create event listeners used to subscribe on the DataSets of the nodes and edges + this.nodesListeners = { + 'add': function (event, params) { + graph._addNodes(params.items); + graph.start(); + }, + 'update': function (event, params) { + graph._updateNodes(params.items); + graph.start(); + }, + 'remove': function (event, params) { + graph._removeNodes(params.items); + graph.start(); + } + }; + this.edgesListeners = { + 'add': function (event, params) { + graph._addEdges(params.items); + graph.start(); + }, + 'update': function (event, params) { + graph._updateEdges(params.items); + graph.start(); + }, + 'remove': function (event, params) { + graph._removeEdges(params.items); + graph.start(); + } + }; + + // properties for the animation + this.moving = true; + this.timer = undefined; // Scheduling function. Is definded in this.start(); + + // load data (the disable start variable will be the same as the enabled clustering) + this.setData(data,this.constants.clustering.enabled || this.constants.hierarchicalLayout.enabled); + + // hierarchical layout + if (this.constants.hierarchicalLayout.enabled == true) { + this._setupHierarchicalLayout(); + } + else { + // zoom so all data will fit on the screen, if clustering is enabled, we do not want start to be called here. + if (this.stabilize == false) { + this.zoomExtent(true,this.constants.clustering.enabled); + } + } + + // if clustering is disabled, the simulation will have started in the setData function + if (this.constants.clustering.enabled) { + this.startWithClustering(); + } +} + +// Extend Graph with an Emitter mixin +Emitter(Graph.prototype); + +/** + * Get the script path where the vis.js library is located + * + * @returns {string | null} path Path or null when not found. Path does not + * end with a slash. + * @private + */ +Graph.prototype._getScriptPath = function() { + var scripts = document.getElementsByTagName( 'script' ); + + // find script named vis.js or vis.min.js + for (var i = 0; i < scripts.length; i++) { + var src = scripts[i].src; + var match = src && /\/?vis(.min)?\.js$/.exec(src); + if (match) { + // return path without the script name + return src.substring(0, src.length - match[0].length); + } + } + + return null; +}; + + +/** + * Find the center position of the graph + * @private + */ +Graph.prototype._getRange = function() { + var minY = 1e9, maxY = -1e9, minX = 1e9, maxX = -1e9, node; + for (var nodeId in this.nodes) { + if (this.nodes.hasOwnProperty(nodeId)) { + node = this.nodes[nodeId]; + if (minX > (node.x)) {minX = node.x;} + if (maxX < (node.x)) {maxX = node.x;} + if (minY > (node.y)) {minY = node.y;} + if (maxY < (node.y)) {maxY = node.y;} + } + } + return {minX: minX, maxX: maxX, minY: minY, maxY: maxY}; +}; + + +/** + * @param {object} range = {minX: minX, maxX: maxX, minY: minY, maxY: maxY}; + * @returns {{x: number, y: number}} + * @private + */ +Graph.prototype._findCenter = function(range) { + return {x: (0.5 * (range.maxX + range.minX)), + y: (0.5 * (range.maxY + range.minY))}; +}; + + +/** + * center the graph + * + * @param {object} range = {minX: minX, maxX: maxX, minY: minY, maxY: maxY}; + */ +Graph.prototype._centerGraph = function(range) { + var center = this._findCenter(range); + + center.x *= this.scale; + center.y *= this.scale; + center.x -= 0.5 * this.frame.canvas.clientWidth; + center.y -= 0.5 * this.frame.canvas.clientHeight; + + this._setTranslation(-center.x,-center.y); // set at 0,0 +}; + + +/** + * This function zooms out to fit all data on screen based on amount of nodes + * + * @param {Boolean} [initialZoom] | zoom based on fitted formula or range, true = fitted, default = false; + */ +Graph.prototype.zoomExtent = function(initialZoom, disableStart) { + if (initialZoom === undefined) { + initialZoom = false; + } + if (disableStart === undefined) { + disableStart = false; + } + + var range = this._getRange(); + var zoomLevel; + + if (initialZoom == true) { + var numberOfNodes = this.nodeIndices.length; + if (this.constants.smoothCurves == true) { + if (this.constants.clustering.enabled == true && + numberOfNodes >= this.constants.clustering.initialMaxNodes) { + zoomLevel = 49.07548 / (numberOfNodes + 142.05338) + 9.1444e-04; // this is obtained from fitting a dataset from 5 points with scale levels that looked good. + } + else { + zoomLevel = 12.662 / (numberOfNodes + 7.4147) + 0.0964822; // this is obtained from fitting a dataset from 5 points with scale levels that looked good. + } + } + else { + if (this.constants.clustering.enabled == true && + numberOfNodes >= this.constants.clustering.initialMaxNodes) { + zoomLevel = 77.5271985 / (numberOfNodes + 187.266146) + 4.76710517e-05; // this is obtained from fitting a dataset from 5 points with scale levels that looked good. + } + else { + zoomLevel = 30.5062972 / (numberOfNodes + 19.93597763) + 0.08413486; // this is obtained from fitting a dataset from 5 points with scale levels that looked good. + } + } + + // correct for larger canvasses. + var factor = Math.min(this.frame.canvas.clientWidth / 600, this.frame.canvas.clientHeight / 600); + zoomLevel *= factor; + } + else { + var xDistance = (Math.abs(range.minX) + Math.abs(range.maxX)) * 1.1; + var yDistance = (Math.abs(range.minY) + Math.abs(range.maxY)) * 1.1; + + var xZoomLevel = this.frame.canvas.clientWidth / xDistance; + var yZoomLevel = this.frame.canvas.clientHeight / yDistance; + + zoomLevel = (xZoomLevel <= yZoomLevel) ? xZoomLevel : yZoomLevel; + } + + if (zoomLevel > 1.0) { + zoomLevel = 1.0; + } + + + this._setScale(zoomLevel); + this._centerGraph(range); + if (disableStart == false) { + this.moving = true; + this.start(); + } +}; + + +/** + * Update the this.nodeIndices with the most recent node index list + * @private + */ +Graph.prototype._updateNodeIndexList = function() { + this._clearNodeIndexList(); + for (var idx in this.nodes) { + if (this.nodes.hasOwnProperty(idx)) { + this.nodeIndices.push(idx); + } + } +}; + + +/** + * Set nodes and edges, and optionally options as well. + * + * @param {Object} data Object containing parameters: + * {Array | DataSet | DataView} [nodes] Array with nodes + * {Array | DataSet | DataView} [edges] Array with edges + * {String} [dot] String containing data in DOT format + * {Options} [options] Object with options + * @param {Boolean} [disableStart] | optional: disable the calling of the start function. + */ +Graph.prototype.setData = function(data, disableStart) { + if (disableStart === undefined) { + disableStart = false; + } + + if (data && data.dot && (data.nodes || data.edges)) { + throw new SyntaxError('Data must contain either parameter "dot" or ' + + ' parameter pair "nodes" and "edges", but not both.'); + } + + // set options + this.setOptions(data && data.options); + + // set all data + if (data && data.dot) { + // parse DOT file + if(data && data.dot) { + var dotData = vis.util.DOTToGraph(data.dot); + this.setData(dotData); + return; + } + } + else { + this._setNodes(data && data.nodes); + this._setEdges(data && data.edges); + } + + this._putDataInSector(); + + if (!disableStart) { + // find a stable position or start animating to a stable position + if (this.stabilize) { + this._stabilize(); + } + this.start(); + } +}; + +/** + * Set options + * @param {Object} options + */ +Graph.prototype.setOptions = function (options) { + if (options) { + var prop; + // retrieve parameter values + if (options.width !== undefined) {this.width = options.width;} + if (options.height !== undefined) {this.height = options.height;} + if (options.stabilize !== undefined) {this.stabilize = options.stabilize;} + if (options.selectable !== undefined) {this.selectable = options.selectable;} + if (options.smoothCurves !== undefined) {this.constants.smoothCurves = options.smoothCurves;} + if (options.freezeForStabilization !== undefined) {this.constants.freezeForStabilization = options.freezeForStabilization;} + if (options.configurePhysics !== undefined){this.constants.configurePhysics = options.configurePhysics;} + if (options.stabilizationIterations !== undefined) {this.constants.stabilizationIterations = options.stabilizationIterations;} + + if (options.onAdd) { + this.triggerFunctions.add = options.onAdd; + } + + if (options.onEdit) { + this.triggerFunctions.edit = options.onEdit; + } + + if (options.onConnect) { + this.triggerFunctions.connect = options.onConnect; + } + + if (options.onDelete) { + this.triggerFunctions.delete = options.onDelete; + } + + if (options.physics) { + if (options.physics.barnesHut) { + this.constants.physics.barnesHut.enabled = true; + for (prop in options.physics.barnesHut) { + if (options.physics.barnesHut.hasOwnProperty(prop)) { + this.constants.physics.barnesHut[prop] = options.physics.barnesHut[prop]; + } + } + } + + if (options.physics.repulsion) { + this.constants.physics.barnesHut.enabled = false; + for (prop in options.physics.repulsion) { + if (options.physics.repulsion.hasOwnProperty(prop)) { + this.constants.physics.repulsion[prop] = options.physics.repulsion[prop]; + } + } + } + } + + if (options.hierarchicalLayout) { + this.constants.hierarchicalLayout.enabled = true; + for (prop in options.hierarchicalLayout) { + if (options.hierarchicalLayout.hasOwnProperty(prop)) { + this.constants.hierarchicalLayout[prop] = options.hierarchicalLayout[prop]; + } + } + } + else if (options.hierarchicalLayout !== undefined) { + this.constants.hierarchicalLayout.enabled = false; + } + + if (options.clustering) { + this.constants.clustering.enabled = true; + for (prop in options.clustering) { + if (options.clustering.hasOwnProperty(prop)) { + this.constants.clustering[prop] = options.clustering[prop]; + } + } + } + else if (options.clustering !== undefined) { + this.constants.clustering.enabled = false; + } + + if (options.navigation) { + this.constants.navigation.enabled = true; + for (prop in options.navigation) { + if (options.navigation.hasOwnProperty(prop)) { + this.constants.navigation[prop] = options.navigation[prop]; + } + } + } + else if (options.navigation !== undefined) { + this.constants.navigation.enabled = false; + } + + if (options.keyboard) { + this.constants.keyboard.enabled = true; + for (prop in options.keyboard) { + if (options.keyboard.hasOwnProperty(prop)) { + this.constants.keyboard[prop] = options.keyboard[prop]; + } + } + } + else if (options.keyboard !== undefined) { + this.constants.keyboard.enabled = false; + } + + if (options.dataManipulation) { + this.constants.dataManipulation.enabled = true; + for (prop in options.dataManipulation) { + if (options.dataManipulation.hasOwnProperty(prop)) { + this.constants.dataManipulation[prop] = options.dataManipulation[prop]; + } + } + } + else if (options.dataManipulation !== undefined) { + this.constants.dataManipulation.enabled = false; + } + + // TODO: work out these options and document them + if (options.edges) { + for (prop in options.edges) { + if (options.edges.hasOwnProperty(prop)) { + if (typeof options.edges[prop] != "object") { + this.constants.edges[prop] = options.edges[prop]; + } + } + } + + if (options.edges.color !== undefined) { + if (util.isString(options.edges.color)) { + this.constants.edges.color.color = options.edges.color; + this.constants.edges.color.highlight = options.edges.color; + } + else { + if (options.edges.color.color !== undefined) {this.constants.edges.color.color = options.edges.color.color;} + if (options.edges.color.highlight !== undefined) {this.constants.edges.color.highlight = options.edges.color.highlight;} + } + } + + if (!options.edges.fontColor) { + if (options.edges.color !== undefined) { + if (util.isString(options.edges.color)) {this.constants.edges.fontColor = options.edges.color;} + else if (options.edges.color.color !== undefined) {this.constants.edges.fontColor = options.edges.color.color;} + } + } + + // Added to support dashed lines + // David Jordan + // 2012-08-08 + if (options.edges.dash) { + if (options.edges.dash.length !== undefined) { + this.constants.edges.dash.length = options.edges.dash.length; + } + if (options.edges.dash.gap !== undefined) { + this.constants.edges.dash.gap = options.edges.dash.gap; + } + if (options.edges.dash.altLength !== undefined) { + this.constants.edges.dash.altLength = options.edges.dash.altLength; + } + } + } + + if (options.nodes) { + for (prop in options.nodes) { + if (options.nodes.hasOwnProperty(prop)) { + this.constants.nodes[prop] = options.nodes[prop]; + } + } + + if (options.nodes.color) { + this.constants.nodes.color = Node.parseColor(options.nodes.color); + } + + /* + if (options.nodes.widthMin) this.constants.nodes.radiusMin = options.nodes.widthMin; + if (options.nodes.widthMax) this.constants.nodes.radiusMax = options.nodes.widthMax; + */ + } + if (options.groups) { + for (var groupname in options.groups) { + if (options.groups.hasOwnProperty(groupname)) { + var group = options.groups[groupname]; + this.groups.add(groupname, group); + } + } + } + } + + + // (Re)loading the mixins that can be enabled or disabled in the options. + // load the force calculation functions, grouped under the physics system. + this._loadPhysicsSystem(); + // load the navigation system. + this._loadNavigationControls(); + // load the data manipulation system + this._loadManipulationSystem(); + // configure the smooth curves + this._configureSmoothCurves(); + + + // bind keys. If disabled, this will not do anything; + this._createKeyBinds(); + + this.setSize(this.width, this.height); + this._setTranslation(this.frame.clientWidth / 2, this.frame.clientHeight / 2); + this._setScale(1); + this._redraw(); +}; + +/** + * Create the main frame for the Graph. + * This function is executed once when a Graph object is created. The frame + * contains a canvas, and this canvas contains all objects like the axis and + * nodes. + * @private + */ +Graph.prototype._create = function () { + // remove all elements from the container element. + while (this.containerElement.hasChildNodes()) { + this.containerElement.removeChild(this.containerElement.firstChild); + } + + this.frame = document.createElement('div'); + this.frame.className = 'graph-frame'; + this.frame.style.position = 'relative'; + this.frame.style.overflow = 'hidden'; + this.frame.style.zIndex = "1"; + + // create the graph canvas (HTML canvas element) + this.frame.canvas = document.createElement( 'canvas' ); + this.frame.canvas.style.position = 'relative'; + this.frame.appendChild(this.frame.canvas); + if (!this.frame.canvas.getContext) { + var noCanvas = document.createElement( 'DIV' ); + noCanvas.style.color = 'red'; + noCanvas.style.fontWeight = 'bold' ; + noCanvas.style.padding = '10px'; + noCanvas.innerHTML = 'Error: your browser does not support HTML canvas'; + this.frame.canvas.appendChild(noCanvas); + } + + var me = this; + this.drag = {}; + this.pinch = {}; + this.hammer = Hammer(this.frame.canvas, { + prevent_default: true + }); + this.hammer.on('tap', me._onTap.bind(me) ); + this.hammer.on('doubletap', me._onDoubleTap.bind(me) ); + this.hammer.on('hold', me._onHold.bind(me) ); + this.hammer.on('pinch', me._onPinch.bind(me) ); + this.hammer.on('touch', me._onTouch.bind(me) ); + this.hammer.on('dragstart', me._onDragStart.bind(me) ); + this.hammer.on('drag', me._onDrag.bind(me) ); + this.hammer.on('dragend', me._onDragEnd.bind(me) ); + this.hammer.on('release', me._onRelease.bind(me) ); + this.hammer.on('mousewheel',me._onMouseWheel.bind(me) ); + this.hammer.on('DOMMouseScroll',me._onMouseWheel.bind(me) ); // for FF + this.hammer.on('mousemove', me._onMouseMoveTitle.bind(me) ); + + // add the frame to the container element + this.containerElement.appendChild(this.frame); + +}; + + +/** + * Binding the keys for keyboard navigation. These functions are defined in the NavigationMixin + * @private + */ +Graph.prototype._createKeyBinds = function() { + var me = this; + this.mousetrap = mousetrap; + + this.mousetrap.reset(); + + if (this.constants.keyboard.enabled == true) { + this.mousetrap.bind("up", this._moveUp.bind(me) , "keydown"); + this.mousetrap.bind("up", this._yStopMoving.bind(me), "keyup"); + this.mousetrap.bind("down", this._moveDown.bind(me) , "keydown"); + this.mousetrap.bind("down", this._yStopMoving.bind(me), "keyup"); + this.mousetrap.bind("left", this._moveLeft.bind(me) , "keydown"); + this.mousetrap.bind("left", this._xStopMoving.bind(me), "keyup"); + this.mousetrap.bind("right",this._moveRight.bind(me), "keydown"); + this.mousetrap.bind("right",this._xStopMoving.bind(me), "keyup"); + this.mousetrap.bind("=", this._zoomIn.bind(me), "keydown"); + this.mousetrap.bind("=", this._stopZoom.bind(me), "keyup"); + this.mousetrap.bind("-", this._zoomOut.bind(me), "keydown"); + this.mousetrap.bind("-", this._stopZoom.bind(me), "keyup"); + this.mousetrap.bind("[", this._zoomIn.bind(me), "keydown"); + this.mousetrap.bind("[", this._stopZoom.bind(me), "keyup"); + this.mousetrap.bind("]", this._zoomOut.bind(me), "keydown"); + this.mousetrap.bind("]", this._stopZoom.bind(me), "keyup"); + this.mousetrap.bind("pageup",this._zoomIn.bind(me), "keydown"); + this.mousetrap.bind("pageup",this._stopZoom.bind(me), "keyup"); + this.mousetrap.bind("pagedown",this._zoomOut.bind(me),"keydown"); + this.mousetrap.bind("pagedown",this._stopZoom.bind(me), "keyup"); + } + + if (this.constants.dataManipulation.enabled == true) { + this.mousetrap.bind("escape",this._createManipulatorBar.bind(me)); + this.mousetrap.bind("del",this._deleteSelected.bind(me)); + } +}; + +/** + * Get the pointer location from a touch location + * @param {{pageX: Number, pageY: Number}} touch + * @return {{x: Number, y: Number}} pointer + * @private + */ +Graph.prototype._getPointer = function (touch) { + return { + x: touch.pageX - vis.util.getAbsoluteLeft(this.frame.canvas), + y: touch.pageY - vis.util.getAbsoluteTop(this.frame.canvas) + }; +}; + +/** + * On start of a touch gesture, store the pointer + * @param event + * @private + */ +Graph.prototype._onTouch = function (event) { + this.drag.pointer = this._getPointer(event.gesture.center); + this.drag.pinched = false; + this.pinch.scale = this._getScale(); + + this._handleTouch(this.drag.pointer); +}; + +/** + * handle drag start event + * @private + */ +Graph.prototype._onDragStart = function () { + this._handleDragStart(); +}; + + +/** + * This function is called by _onDragStart. + * It is separated out because we can then overload it for the datamanipulation system. + * + * @private + */ +Graph.prototype._handleDragStart = function() { + var drag = this.drag; + var node = this._getNodeAt(drag.pointer); + // note: drag.pointer is set in _onTouch to get the initial touch location + + drag.dragging = true; + drag.selection = []; + drag.translation = this._getTranslation(); + drag.nodeId = null; + + if (node != null) { + drag.nodeId = node.id; + // select the clicked node if not yet selected + if (!node.isSelected()) { + this._selectObject(node,false); + } + + // create an array with the selected nodes and their original location and status + for (var objectId in this.selectionObj.nodes) { + if (this.selectionObj.nodes.hasOwnProperty(objectId)) { + var object = this.selectionObj.nodes[objectId]; + var s = { + id: object.id, + node: object, + + // store original x, y, xFixed and yFixed, make the node temporarily Fixed + x: object.x, + y: object.y, + xFixed: object.xFixed, + yFixed: object.yFixed + }; + + object.xFixed = true; + object.yFixed = true; + + drag.selection.push(s); + } + } + } +}; + + +/** + * handle drag event + * @private + */ +Graph.prototype._onDrag = function (event) { + this._handleOnDrag(event) +}; + + +/** + * This function is called by _onDrag. + * It is separated out because we can then overload it for the datamanipulation system. + * + * @private + */ +Graph.prototype._handleOnDrag = function(event) { + if (this.drag.pinched) { + return; + } + + var pointer = this._getPointer(event.gesture.center); + + var me = this, + drag = this.drag, + selection = drag.selection; + if (selection && selection.length) { + // calculate delta's and new location + var deltaX = pointer.x - drag.pointer.x, + deltaY = pointer.y - drag.pointer.y; + + // update position of all selected nodes + selection.forEach(function (s) { + var node = s.node; + + if (!s.xFixed) { + node.x = me._canvasToX(me._xToCanvas(s.x) + deltaX); + } + + if (!s.yFixed) { + node.y = me._canvasToY(me._yToCanvas(s.y) + deltaY); + } + }); + + // start _animationStep if not yet running + if (!this.moving) { + this.moving = true; + this.start(); + } + } + else { + // move the graph + var diffX = pointer.x - this.drag.pointer.x; + var diffY = pointer.y - this.drag.pointer.y; + + this._setTranslation( + this.drag.translation.x + diffX, + this.drag.translation.y + diffY); + this._redraw(); + this.moved = true; + } +}; + +/** + * handle drag start event + * @private + */ +Graph.prototype._onDragEnd = function () { + this.drag.dragging = false; + var selection = this.drag.selection; + if (selection) { + selection.forEach(function (s) { + // restore original xFixed and yFixed + s.node.xFixed = s.xFixed; + s.node.yFixed = s.yFixed; + }); + } +}; + +/** + * handle tap/click event: select/unselect a node + * @private + */ +Graph.prototype._onTap = function (event) { + var pointer = this._getPointer(event.gesture.center); + this.pointerPosition = pointer; + this._handleTap(pointer); + +}; + + +/** + * handle doubletap event + * @private + */ +Graph.prototype._onDoubleTap = function (event) { + var pointer = this._getPointer(event.gesture.center); + this._handleDoubleTap(pointer); +}; + + +/** + * handle long tap event: multi select nodes + * @private + */ +Graph.prototype._onHold = function (event) { + var pointer = this._getPointer(event.gesture.center); + this.pointerPosition = pointer; + this._handleOnHold(pointer); +}; + +/** + * handle the release of the screen + * + * @private + */ +Graph.prototype._onRelease = function (event) { + var pointer = this._getPointer(event.gesture.center); + this._handleOnRelease(pointer); +}; + +/** + * Handle pinch event + * @param event + * @private + */ +Graph.prototype._onPinch = function (event) { + var pointer = this._getPointer(event.gesture.center); + + this.drag.pinched = true; + if (!('scale' in this.pinch)) { + this.pinch.scale = 1; + } + + // TODO: enabled moving while pinching? + var scale = this.pinch.scale * event.gesture.scale; + this._zoom(scale, pointer) +}; + +/** + * Zoom the graph in or out + * @param {Number} scale a number around 1, and between 0.01 and 10 + * @param {{x: Number, y: Number}} pointer Position on screen + * @return {Number} appliedScale scale is limited within the boundaries + * @private + */ +Graph.prototype._zoom = function(scale, pointer) { + var scaleOld = this._getScale(); + if (scale < 0.00001) { + scale = 0.00001; + } + if (scale > 10) { + scale = 10; + } +// + this.frame.canvas.clientHeight / 2 + var translation = this._getTranslation(); + + var scaleFrac = scale / scaleOld; + var tx = (1 - scaleFrac) * pointer.x + translation.x * scaleFrac; + var ty = (1 - scaleFrac) * pointer.y + translation.y * scaleFrac; + + this.areaCenter = {"x" : this._canvasToX(pointer.x), + "y" : this._canvasToY(pointer.y)}; + + this._setScale(scale); + this._setTranslation(tx, ty); + this.updateClustersDefault(); + this._redraw(); + + return scale; +}; + + +/** + * Event handler for mouse wheel event, used to zoom the timeline + * See http://adomas.org/javascript-mouse-wheel/ + * https://github.com/EightMedia/hammer.js/issues/256 + * @param {MouseEvent} event + * @private + */ +Graph.prototype._onMouseWheel = function(event) { + // retrieve delta + var delta = 0; + if (event.wheelDelta) { /* IE/Opera. */ + delta = event.wheelDelta/120; + } else if (event.detail) { /* Mozilla case. */ + // In Mozilla, sign of delta is different than in IE. + // Also, delta is multiple of 3. + delta = -event.detail/3; + } + + // If delta is nonzero, handle it. + // Basically, delta is now positive if wheel was scrolled up, + // and negative, if wheel was scrolled down. + if (delta) { + + // calculate the new scale + var scale = this._getScale(); + var zoom = delta / 10; + if (delta < 0) { + zoom = zoom / (1 - zoom); + } + scale *= (1 + zoom); + + // calculate the pointer location + var gesture = util.fakeGesture(this, event); + var pointer = this._getPointer(gesture.center); + + // apply the new scale + this._zoom(scale, pointer); + } + + // Prevent default actions caused by mouse wheel. + event.preventDefault(); +}; + + +/** + * Mouse move handler for checking whether the title moves over a node with a title. + * @param {Event} event + * @private + */ +Graph.prototype._onMouseMoveTitle = function (event) { + var gesture = util.fakeGesture(this, event); + var pointer = this._getPointer(gesture.center); + + // check if the previously selected node is still selected + if (this.popupNode) { + this._checkHidePopup(pointer); + } + + // start a timeout that will check if the mouse is positioned above + // an element + var me = this; + var checkShow = function() { + me._checkShowPopup(pointer); + }; + if (this.popupTimer) { + clearInterval(this.popupTimer); // stop any running calculationTimer + } + if (!this.drag.dragging) { + this.popupTimer = setTimeout(checkShow, 300); + } +}; + +/** + * Check if there is an element on the given position in the graph + * (a node or edge). If so, and if this element has a title, + * show a popup window with its title. + * + * @param {{x:Number, y:Number}} pointer + * @private + */ +Graph.prototype._checkShowPopup = function (pointer) { + var obj = { + left: this._canvasToX(pointer.x), + top: this._canvasToY(pointer.y), + right: this._canvasToX(pointer.x), + bottom: this._canvasToY(pointer.y) + }; + + var id; + var lastPopupNode = this.popupNode; + + if (this.popupNode == undefined) { + // search the nodes for overlap, select the top one in case of multiple nodes + var nodes = this.nodes; + for (id in nodes) { + if (nodes.hasOwnProperty(id)) { + var node = nodes[id]; + if (node.getTitle() !== undefined && node.isOverlappingWith(obj)) { + this.popupNode = node; + break; + } + } + } + } + + if (this.popupNode === undefined) { + // search the edges for overlap + var edges = this.edges; + for (id in edges) { + if (edges.hasOwnProperty(id)) { + var edge = edges[id]; + if (edge.connected && (edge.getTitle() !== undefined) && + edge.isOverlappingWith(obj)) { + this.popupNode = edge; + break; + } + } + } + } + + if (this.popupNode) { + // show popup message window + if (this.popupNode != lastPopupNode) { + var me = this; + if (!me.popup) { + me.popup = new Popup(me.frame); + } + + // adjust a small offset such that the mouse cursor is located in the + // bottom left location of the popup, and you can easily move over the + // popup area + me.popup.setPosition(pointer.x - 3, pointer.y - 3); + me.popup.setText(me.popupNode.getTitle()); + me.popup.show(); + } + } + else { + if (this.popup) { + this.popup.hide(); + } + } +}; + + +/** + * Check if the popup must be hided, which is the case when the mouse is no + * longer hovering on the object + * @param {{x:Number, y:Number}} pointer + * @private + */ +Graph.prototype._checkHidePopup = function (pointer) { + if (!this.popupNode || !this._getNodeAt(pointer) ) { + this.popupNode = undefined; + if (this.popup) { + this.popup.hide(); + } + } +}; + + +/** + * Set a new size for the graph + * @param {string} width Width in pixels or percentage (for example '800px' + * or '50%') + * @param {string} height Height in pixels or percentage (for example '400px' + * or '30%') + */ +Graph.prototype.setSize = function(width, height) { + this.frame.style.width = width; + this.frame.style.height = height; + + this.frame.canvas.style.width = '100%'; + this.frame.canvas.style.height = '100%'; + + this.frame.canvas.width = this.frame.canvas.clientWidth; + this.frame.canvas.height = this.frame.canvas.clientHeight; + + if (this.manipulationDiv !== undefined) { + this.manipulationDiv.style.width = this.frame.canvas.clientWidth + "px"; + } + if (this.navigationDivs !== undefined) { + if (this.navigationDivs['wrapper'] !== undefined) { + this.navigationDivs['wrapper'].style.width = this.frame.canvas.clientWidth + "px"; + this.navigationDivs['wrapper'].style.height = this.frame.canvas.clientHeight + "px"; + } + } + + this.emit('resize', {width:this.frame.canvas.width,height:this.frame.canvas.height}); +}; + +/** + * Set a data set with nodes for the graph + * @param {Array | DataSet | DataView} nodes The data containing the nodes. + * @private + */ +Graph.prototype._setNodes = function(nodes) { + var oldNodesData = this.nodesData; + + if (nodes instanceof DataSet || nodes instanceof DataView) { + this.nodesData = nodes; + } + else if (nodes instanceof Array) { + this.nodesData = new DataSet(); + this.nodesData.add(nodes); + } + else if (!nodes) { + this.nodesData = new DataSet(); + } + else { + throw new TypeError('Array or DataSet expected'); + } + + if (oldNodesData) { + // unsubscribe from old dataset + util.forEach(this.nodesListeners, function (callback, event) { + oldNodesData.off(event, callback); + }); + } + + // remove drawn nodes + this.nodes = {}; + + if (this.nodesData) { + // subscribe to new dataset + var me = this; + util.forEach(this.nodesListeners, function (callback, event) { + me.nodesData.on(event, callback); + }); + + // draw all new nodes + var ids = this.nodesData.getIds(); + this._addNodes(ids); + } + this._updateSelection(); +}; + +/** + * Add nodes + * @param {Number[] | String[]} ids + * @private + */ +Graph.prototype._addNodes = function(ids) { + var id; + for (var i = 0, len = ids.length; i < len; i++) { + id = ids[i]; + var data = this.nodesData.get(id); + var node = new Node(data, this.images, this.groups, this.constants); + this.nodes[id] = node; // note: this may replace an existing node + + if ((node.xFixed == false || node.yFixed == false) && (node.x === null || node.y === null)) { + var radius = 10 * 0.1*ids.length; + var angle = 2 * Math.PI * Math.random(); + if (node.xFixed == false) {node.x = radius * Math.cos(angle);} + if (node.yFixed == false) {node.y = radius * Math.sin(angle);} + } + this.moving = true; + } + this._updateNodeIndexList(); + this._updateCalculationNodes(); + this._reconnectEdges(); + this._updateValueRange(this.nodes); + this.updateLabels(); +}; + +/** + * Update existing nodes, or create them when not yet existing + * @param {Number[] | String[]} ids + * @private + */ +Graph.prototype._updateNodes = function(ids) { + var nodes = this.nodes, + nodesData = this.nodesData; + for (var i = 0, len = ids.length; i < len; i++) { + var id = ids[i]; + var node = nodes[id]; + var data = nodesData.get(id); + if (node) { + // update node + node.setProperties(data, this.constants); + } + else { + // create node + node = new Node(properties, this.images, this.groups, this.constants); + nodes[id] = node; + + if (!node.isFixed()) { + this.moving = true; + } + } + } + this._updateNodeIndexList(); + this._reconnectEdges(); + this._updateValueRange(nodes); +}; + +/** + * Remove existing nodes. If nodes do not exist, the method will just ignore it. + * @param {Number[] | String[]} ids + * @private + */ +Graph.prototype._removeNodes = function(ids) { + var nodes = this.nodes; + for (var i = 0, len = ids.length; i < len; i++) { + var id = ids[i]; + delete nodes[id]; + } + this._updateNodeIndexList(); + this._updateCalculationNodes(); + this._reconnectEdges(); + this._updateSelection(); + this._updateValueRange(nodes); +}; + +/** + * Load edges by reading the data table + * @param {Array | DataSet | DataView} edges The data containing the edges. + * @private + * @private + */ +Graph.prototype._setEdges = function(edges) { + var oldEdgesData = this.edgesData; + + if (edges instanceof DataSet || edges instanceof DataView) { + this.edgesData = edges; + } + else if (edges instanceof Array) { + this.edgesData = new DataSet(); + this.edgesData.add(edges); + } + else if (!edges) { + this.edgesData = new DataSet(); + } + else { + throw new TypeError('Array or DataSet expected'); + } + + if (oldEdgesData) { + // unsubscribe from old dataset + util.forEach(this.edgesListeners, function (callback, event) { + oldEdgesData.off(event, callback); + }); + } + + // remove drawn edges + this.edges = {}; + + if (this.edgesData) { + // subscribe to new dataset + var me = this; + util.forEach(this.edgesListeners, function (callback, event) { + me.edgesData.on(event, callback); + }); + + // draw all new nodes + var ids = this.edgesData.getIds(); + this._addEdges(ids); + } + + this._reconnectEdges(); +}; + +/** + * Add edges + * @param {Number[] | String[]} ids + * @private + */ +Graph.prototype._addEdges = function (ids) { + var edges = this.edges, + edgesData = this.edgesData; + + for (var i = 0, len = ids.length; i < len; i++) { + var id = ids[i]; + + var oldEdge = edges[id]; + if (oldEdge) { + oldEdge.disconnect(); + } + + var data = edgesData.get(id, {"showInternalIds" : true}); + edges[id] = new Edge(data, this, this.constants); + } + + this.moving = true; + this._updateValueRange(edges); + this._createBezierNodes(); + this._updateCalculationNodes(); +}; + +/** + * Update existing edges, or create them when not yet existing + * @param {Number[] | String[]} ids + * @private + */ +Graph.prototype._updateEdges = function (ids) { + var edges = this.edges, + edgesData = this.edgesData; + for (var i = 0, len = ids.length; i < len; i++) { + var id = ids[i]; + + var data = edgesData.get(id); + var edge = edges[id]; + if (edge) { + // update edge + edge.disconnect(); + edge.setProperties(data, this.constants); + edge.connect(); + } + else { + // create edge + edge = new Edge(data, this, this.constants); + this.edges[id] = edge; + } + } + + this._createBezierNodes(); + this.moving = true; + this._updateValueRange(edges); +}; + +/** + * Remove existing edges. Non existing ids will be ignored + * @param {Number[] | String[]} ids + * @private + */ +Graph.prototype._removeEdges = function (ids) { + var edges = this.edges; + for (var i = 0, len = ids.length; i < len; i++) { + var id = ids[i]; + var edge = edges[id]; + if (edge) { + if (edge.via != null) { + delete this.sectors['support']['nodes'][edge.via.id]; + } + edge.disconnect(); + delete edges[id]; + } + } + + this.moving = true; + this._updateValueRange(edges); + this._updateCalculationNodes(); +}; + +/** + * Reconnect all edges + * @private + */ +Graph.prototype._reconnectEdges = function() { + var id, + nodes = this.nodes, + edges = this.edges; + for (id in nodes) { + if (nodes.hasOwnProperty(id)) { + nodes[id].edges = []; + } + } + + for (id in edges) { + if (edges.hasOwnProperty(id)) { + var edge = edges[id]; + edge.from = null; + edge.to = null; + edge.connect(); + } + } +}; + +/** + * Update the values of all object in the given array according to the current + * value range of the objects in the array. + * @param {Object} obj An object containing a set of Edges or Nodes + * The objects must have a method getValue() and + * setValueRange(min, max). + * @private + */ +Graph.prototype._updateValueRange = function(obj) { + var id; + + // determine the range of the objects + var valueMin = undefined; + var valueMax = undefined; + for (id in obj) { + if (obj.hasOwnProperty(id)) { + var value = obj[id].getValue(); + if (value !== undefined) { + valueMin = (valueMin === undefined) ? value : Math.min(value, valueMin); + valueMax = (valueMax === undefined) ? value : Math.max(value, valueMax); + } + } + } + + // adjust the range of all objects + if (valueMin !== undefined && valueMax !== undefined) { + for (id in obj) { + if (obj.hasOwnProperty(id)) { + obj[id].setValueRange(valueMin, valueMax); + } + } + } +}; + +/** + * Redraw the graph with the current data + * chart will be resized too. + */ +Graph.prototype.redraw = function() { + this.setSize(this.width, this.height); + + this._redraw(); +}; + +/** + * Redraw the graph with the current data + * @private + */ +Graph.prototype._redraw = function() { + var ctx = this.frame.canvas.getContext('2d'); + // clear the canvas + var w = this.frame.canvas.width; + var h = this.frame.canvas.height; + ctx.clearRect(0, 0, w, h); + + // set scaling and translation + ctx.save(); + ctx.translate(this.translation.x, this.translation.y); + ctx.scale(this.scale, this.scale); + + this.canvasTopLeft = { + "x": this._canvasToX(0), + "y": this._canvasToY(0) + }; + this.canvasBottomRight = { + "x": this._canvasToX(this.frame.canvas.clientWidth), + "y": this._canvasToY(this.frame.canvas.clientHeight) + }; + + this._doInAllSectors("_drawAllSectorNodes",ctx); + this._doInAllSectors("_drawEdges",ctx); + this._doInAllSectors("_drawNodes",ctx,false); + +// this._doInSupportSector("_drawNodes",ctx,true); +// this._drawTree(ctx,"#F00F0F"); + + // restore original scaling and translation + ctx.restore(); +}; + +/** + * Set the translation of the graph + * @param {Number} offsetX Horizontal offset + * @param {Number} offsetY Vertical offset + * @private + */ +Graph.prototype._setTranslation = function(offsetX, offsetY) { + if (this.translation === undefined) { + this.translation = { + x: 0, + y: 0 + }; + } + + if (offsetX !== undefined) { + this.translation.x = offsetX; + } + if (offsetY !== undefined) { + this.translation.y = offsetY; + } +}; + +/** + * Get the translation of the graph + * @return {Object} translation An object with parameters x and y, both a number + * @private + */ +Graph.prototype._getTranslation = function() { + return { + x: this.translation.x, + y: this.translation.y + }; +}; + +/** + * Scale the graph + * @param {Number} scale Scaling factor 1.0 is unscaled + * @private + */ +Graph.prototype._setScale = function(scale) { + this.scale = scale; +}; + +/** + * Get the current scale of the graph + * @return {Number} scale Scaling factor 1.0 is unscaled + * @private + */ +Graph.prototype._getScale = function() { + return this.scale; +}; + +/** + * Convert a horizontal point on the HTML canvas to the x-value of the model + * @param {number} x + * @returns {number} + * @private + */ +Graph.prototype._canvasToX = function(x) { + return (x - this.translation.x) / this.scale; +}; + +/** + * Convert an x-value in the model to a horizontal point on the HTML canvas + * @param {number} x + * @returns {number} + * @private + */ +Graph.prototype._xToCanvas = function(x) { + return x * this.scale + this.translation.x; +}; + +/** + * Convert a vertical point on the HTML canvas to the y-value of the model + * @param {number} y + * @returns {number} + * @private + */ +Graph.prototype._canvasToY = function(y) { + return (y - this.translation.y) / this.scale; +}; + +/** + * Convert an y-value in the model to a vertical point on the HTML canvas + * @param {number} y + * @returns {number} + * @private + */ +Graph.prototype._yToCanvas = function(y) { + return y * this.scale + this.translation.y ; +}; + +/** + * Redraw all nodes + * The 2d context of a HTML canvas can be retrieved by canvas.getContext('2d'); + * @param {CanvasRenderingContext2D} ctx + * @param {Boolean} [alwaysShow] + * @private + */ +Graph.prototype._drawNodes = function(ctx,alwaysShow) { + if (alwaysShow === undefined) { + alwaysShow = false; + } + + // first draw the unselected nodes + var nodes = this.nodes; + var selected = []; + + for (var id in nodes) { + if (nodes.hasOwnProperty(id)) { + nodes[id].setScaleAndPos(this.scale,this.canvasTopLeft,this.canvasBottomRight); + if (nodes[id].isSelected()) { + selected.push(id); + } + else { + if (nodes[id].inArea() || alwaysShow) { + nodes[id].draw(ctx); + } + } + } + } + + // draw the selected nodes on top + for (var s = 0, sMax = selected.length; s < sMax; s++) { + if (nodes[selected[s]].inArea() || alwaysShow) { + nodes[selected[s]].draw(ctx); + } + } +}; + +/** + * Redraw all edges + * The 2d context of a HTML canvas can be retrieved by canvas.getContext('2d'); + * @param {CanvasRenderingContext2D} ctx + * @private + */ +Graph.prototype._drawEdges = function(ctx) { + var edges = this.edges; + for (var id in edges) { + if (edges.hasOwnProperty(id)) { + var edge = edges[id]; + edge.setScale(this.scale); + if (edge.connected) { + edges[id].draw(ctx); + } + } + } +}; + +/** + * Find a stable position for all nodes + * @private + */ +Graph.prototype._stabilize = function() { + if (this.constants.freezeForStabilization == true) { + this._freezeDefinedNodes(); + } + + // find stable position + var count = 0; + while (this.moving && count < this.constants.stabilizationIterations) { + this._physicsTick(); + count++; + } + this.zoomExtent(false,true); + if (this.constants.freezeForStabilization == true) { + this._restoreFrozenNodes(); + } + this.emit("stabilized",{iterations:count}); +}; + + +Graph.prototype._freezeDefinedNodes = function() { + var nodes = this.nodes; + for (var id in nodes) { + if (nodes.hasOwnProperty(id)) { + if (nodes[id].x != null && nodes[id].y != null) { + nodes[id].fixedData.x = nodes[id].xFixed; + nodes[id].fixedData.y = nodes[id].yFixed; + nodes[id].xFixed = true; + nodes[id].yFixed = true; + } + } + } +}; + +Graph.prototype._restoreFrozenNodes = function() { + var nodes = this.nodes; + for (var id in nodes) { + if (nodes.hasOwnProperty(id)) { + if (nodes[id].fixedData.x != null) { + nodes[id].xFixed = nodes[id].fixedData.x; + nodes[id].yFixed = nodes[id].fixedData.y; + } + } + } +}; + + +/** + * Check if any of the nodes is still moving + * @param {number} vmin the minimum velocity considered as 'moving' + * @return {boolean} true if moving, false if non of the nodes is moving + * @private + */ +Graph.prototype._isMoving = function(vmin) { + var nodes = this.nodes; + for (var id in nodes) { + if (nodes.hasOwnProperty(id) && nodes[id].isMoving(vmin)) { + return true; + } + } + return false; +}; + + +/** + * /** + * Perform one discrete step for all nodes + * + * @private + */ +Graph.prototype._discreteStepNodes = function() { + var interval = this.physicsDiscreteStepsize; + var nodes = this.nodes; + var nodeId; + + if (this.constants.maxVelocity > 0) { + for (nodeId in nodes) { + if (nodes.hasOwnProperty(nodeId)) { + nodes[nodeId].discreteStepLimited(interval, this.constants.maxVelocity); + } + } + } + else { + for (nodeId in nodes) { + if (nodes.hasOwnProperty(nodeId)) { + nodes[nodeId].discreteStep(interval); + } + } + } + var vminCorrected = this.constants.minVelocity / Math.max(this.scale,0.05); + if (vminCorrected > 0.5*this.constants.maxVelocity) { + this.moving = true; + } + else { + this.moving = this._isMoving(vminCorrected); + } +}; + + +Graph.prototype._physicsTick = function() { + if (!this.freezeSimulation) { + if (this.moving) { + this._doInAllActiveSectors("_initializeForceCalculation"); + this._doInAllActiveSectors("_discreteStepNodes"); + if (this.constants.smoothCurves) { + this._doInSupportSector("_discreteStepNodes"); + } + this._findCenter(this._getRange()) + } + } +}; + + +/** + * This function runs one step of the animation. It calls an x amount of physics ticks and one render tick. + * It reschedules itself at the beginning of the function + * + * @private + */ +Graph.prototype._animationStep = function() { + // reset the timer so a new scheduled animation step can be set + this.timer = undefined; + // handle the keyboad movement + this._handleNavigation(); + + // this schedules a new animation step + this.start(); + + // start the physics simulation + var calculationTime = Date.now(); + var maxSteps = 1; + this._physicsTick(); + var timeRequired = Date.now() - calculationTime; + while (timeRequired < (this.renderTimestep - this.renderTime) && maxSteps < this.maxPhysicsTicksPerRender) { + this._physicsTick(); + timeRequired = Date.now() - calculationTime; + maxSteps++; + + } + + // start the rendering process + var renderTime = Date.now(); + this._redraw(); + this.renderTime = Date.now() - renderTime; +}; + +if (typeof window !== 'undefined') { + window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || + window.webkitRequestAnimationFrame || window.msRequestAnimationFrame; +} + +/** + * Schedule a animation step with the refreshrate interval. + * + * @poram {Boolean} runCalculationStep + */ +Graph.prototype.start = function() { + if (this.moving || this.xIncrement != 0 || this.yIncrement != 0 || this.zoomIncrement != 0) { + if (!this.timer) { + this.timer = window.requestAnimationFrame(this._animationStep.bind(this), this.renderTimestep); // wait this.renderTimeStep milliseconds and perform the animation step function + } + } + else { + this._redraw(); + } +}; + + +/** + * Move the graph according to the keyboard presses. + * + * @private + */ +Graph.prototype._handleNavigation = function() { + if (this.xIncrement != 0 || this.yIncrement != 0) { + var translation = this._getTranslation(); + this._setTranslation(translation.x+this.xIncrement, translation.y+this.yIncrement); + } + if (this.zoomIncrement != 0) { + var center = { + x: this.frame.canvas.clientWidth / 2, + y: this.frame.canvas.clientHeight / 2 + }; + this._zoom(this.scale*(1 + this.zoomIncrement), center); + } +}; + + +/** + * Freeze the _animationStep + */ +Graph.prototype.toggleFreeze = function() { + if (this.freezeSimulation == false) { + this.freezeSimulation = true; + } + else { + this.freezeSimulation = false; + this.start(); + } +}; + + + +Graph.prototype._configureSmoothCurves = function(disableStart) { + if (disableStart === undefined) { + disableStart = true; + } + + if (this.constants.smoothCurves == true) { + this._createBezierNodes(); + } + else { + // delete the support nodes + this.sectors['support']['nodes'] = {}; + for (var edgeId in this.edges) { + if (this.edges.hasOwnProperty(edgeId)) { + this.edges[edgeId].smooth = false; + this.edges[edgeId].via = null; + } + } + } + this._updateCalculationNodes(); + if (!disableStart) { + this.moving = true; + this.start(); + } +}; + +Graph.prototype._createBezierNodes = function() { + if (this.constants.smoothCurves == true) { + for (var edgeId in this.edges) { + if (this.edges.hasOwnProperty(edgeId)) { + var edge = this.edges[edgeId]; + if (edge.via == null) { + edge.smooth = true; + var nodeId = "edgeId:".concat(edge.id); + this.sectors['support']['nodes'][nodeId] = new Node( + {id:nodeId, + mass:1, + shape:'circle', + image:"", + internalMultiplier:1 + },{},{},this.constants); + edge.via = this.sectors['support']['nodes'][nodeId]; + edge.via.parentEdgeId = edge.id; + edge.positionBezierNode(); + } + } + } + } +}; + + +Graph.prototype._initializeMixinLoaders = function () { + for (var mixinFunction in graphMixinLoaders) { + if (graphMixinLoaders.hasOwnProperty(mixinFunction)) { + Graph.prototype[mixinFunction] = graphMixinLoaders[mixinFunction]; + } + } +}; + +/** + * Load the XY positions of the nodes into the dataset. + */ +Graph.prototype.storePosition = function() { + var dataArray = []; + for (var nodeId in this.nodes) { + if (this.nodes.hasOwnProperty(nodeId)) { + var node = this.nodes[nodeId]; + var allowedToMoveX = !this.nodes.xFixed; + var allowedToMoveY = !this.nodes.yFixed; + if (this.nodesData.data[nodeId].x != Math.round(node.x) || this.nodesData.data[nodeId].y != Math.round(node.y)) { + dataArray.push({id:nodeId,x:Math.round(node.x),y:Math.round(node.y),allowedToMoveX:allowedToMoveX,allowedToMoveY:allowedToMoveY}); + } + } + } + this.nodesData.update(dataArray); +}; + + + + + + + + + + + + + +/** + * vis.js module exports + */ +var vis = { + util: util, + + Controller: Controller, + DataSet: DataSet, + DataView: DataView, + Range: Range, + Stack: Stack, + TimeStep: TimeStep, + + components: { + items: { + Item: Item, + ItemBox: ItemBox, + ItemPoint: ItemPoint, + ItemRange: ItemRange + }, + + Component: Component, + Panel: Panel, + RootPanel: RootPanel, + ItemSet: ItemSet, + TimeAxis: TimeAxis + }, + + graph: { + Node: Node, + Edge: Edge, + Popup: Popup, + Groups: Groups, + Images: Images + }, + + Timeline: Timeline, + Graph: Graph +}; + +/** + * CommonJS module exports + */ +if (typeof exports !== 'undefined') { + exports = vis; +} +if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') { + module.exports = vis; +} + +/** + * AMD module exports + */ +if (typeof(define) === 'function') { + define(function () { + return vis; + }); +} + +/** + * Window exports + */ +if (typeof window !== 'undefined') { + // attach the module to the window, load as a regular javascript file + window['vis'] = vis; +} + + +},{"emitter-component":2,"hammerjs":3,"moment":4,"mousetrap":5}],2:[function(require,module,exports){ + +/** + * Expose `Emitter`. + */ + +module.exports = Emitter; + +/** + * Initialize a new `Emitter`. + * + * @api public + */ + +function Emitter(obj) { + if (obj) return mixin(obj); +}; + +/** + * Mixin the emitter properties. + * + * @param {Object} obj + * @return {Object} + * @api private + */ + +function mixin(obj) { + for (var key in Emitter.prototype) { + obj[key] = Emitter.prototype[key]; + } + return obj; +} + +/** + * Listen on the given `event` with `fn`. + * + * @param {String} event + * @param {Function} fn + * @return {Emitter} + * @api public + */ + +Emitter.prototype.on = +Emitter.prototype.addEventListener = function(event, fn){ + this._callbacks = this._callbacks || {}; + (this._callbacks[event] = this._callbacks[event] || []) + .push(fn); + return this; +}; + +/** + * Adds an `event` listener that will be invoked a single + * time then automatically removed. + * + * @param {String} event + * @param {Function} fn + * @return {Emitter} + * @api public + */ + +Emitter.prototype.once = function(event, fn){ + var self = this; + this._callbacks = this._callbacks || {}; + + function on() { + self.off(event, on); + fn.apply(this, arguments); + } + + on.fn = fn; + this.on(event, on); + return this; +}; + +/** + * Remove the given callback for `event` or all + * registered callbacks. + * + * @param {String} event + * @param {Function} fn + * @return {Emitter} + * @api public + */ + +Emitter.prototype.off = +Emitter.prototype.removeListener = +Emitter.prototype.removeAllListeners = +Emitter.prototype.removeEventListener = function(event, fn){ + this._callbacks = this._callbacks || {}; + + // all + if (0 == arguments.length) { + this._callbacks = {}; + return this; + } + + // specific event + var callbacks = this._callbacks[event]; + if (!callbacks) return this; + + // remove all handlers + if (1 == arguments.length) { + delete this._callbacks[event]; + return this; + } + + // remove specific handler + var cb; + for (var i = 0; i < callbacks.length; i++) { + cb = callbacks[i]; + if (cb === fn || cb.fn === fn) { + callbacks.splice(i, 1); + break; + } + } + return this; +}; + +/** + * Emit `event` with the given args. + * + * @param {String} event + * @param {Mixed} ... + * @return {Emitter} + */ + +Emitter.prototype.emit = function(event){ + this._callbacks = this._callbacks || {}; + var args = [].slice.call(arguments, 1) + , callbacks = this._callbacks[event]; + + if (callbacks) { + callbacks = callbacks.slice(0); + for (var i = 0, len = callbacks.length; i < len; ++i) { + callbacks[i].apply(this, args); + } + } + + return this; +}; + +/** + * Return array of callbacks for `event`. + * + * @param {String} event + * @return {Array} + * @api public + */ + +Emitter.prototype.listeners = function(event){ + this._callbacks = this._callbacks || {}; + return this._callbacks[event] || []; +}; + +/** + * Check if this emitter has `event` handlers. + * + * @param {String} event + * @return {Boolean} + * @api public + */ + +Emitter.prototype.hasListeners = function(event){ + return !! this.listeners(event).length; +}; + +},{}],3:[function(require,module,exports){ +/*! Hammer.JS - v1.0.5 - 2013-04-07 + * http://eightmedia.github.com/hammer.js + * + * Copyright (c) 2013 Jorik Tangelder ; + * Licensed under the MIT license */ + +(function(window, undefined) { + 'use strict'; + +/** + * Hammer + * use this to create instances + * @param {HTMLElement} element + * @param {Object} options + * @returns {Hammer.Instance} + * @constructor + */ +var Hammer = function(element, options) { + return new Hammer.Instance(element, options || {}); +}; + +// default settings +Hammer.defaults = { + // add styles and attributes to the element to prevent the browser from doing + // its native behavior. this doesnt prevent the scrolling, but cancels + // the contextmenu, tap highlighting etc + // set to false to disable this + stop_browser_behavior: { + // this also triggers onselectstart=false for IE + userSelect: 'none', + // this makes the element blocking in IE10 >, you could experiment with the value + // see for more options this issue; https://github.com/EightMedia/hammer.js/issues/241 + touchAction: 'none', + touchCallout: 'none', + contentZooming: 'none', + userDrag: 'none', + tapHighlightColor: 'rgba(0,0,0,0)' + } + + // more settings are defined per gesture at gestures.js +}; + +// detect touchevents +Hammer.HAS_POINTEREVENTS = navigator.pointerEnabled || navigator.msPointerEnabled; +Hammer.HAS_TOUCHEVENTS = ('ontouchstart' in window); + +// dont use mouseevents on mobile devices +Hammer.MOBILE_REGEX = /mobile|tablet|ip(ad|hone|od)|android/i; +Hammer.NO_MOUSEEVENTS = Hammer.HAS_TOUCHEVENTS && navigator.userAgent.match(Hammer.MOBILE_REGEX); + +// eventtypes per touchevent (start, move, end) +// are filled by Hammer.event.determineEventTypes on setup +Hammer.EVENT_TYPES = {}; + +// direction defines +Hammer.DIRECTION_DOWN = 'down'; +Hammer.DIRECTION_LEFT = 'left'; +Hammer.DIRECTION_UP = 'up'; +Hammer.DIRECTION_RIGHT = 'right'; + +// pointer type +Hammer.POINTER_MOUSE = 'mouse'; +Hammer.POINTER_TOUCH = 'touch'; +Hammer.POINTER_PEN = 'pen'; + +// touch event defines +Hammer.EVENT_START = 'start'; +Hammer.EVENT_MOVE = 'move'; +Hammer.EVENT_END = 'end'; + +// hammer document where the base events are added at +Hammer.DOCUMENT = document; + +// plugins namespace +Hammer.plugins = {}; + +// if the window events are set... +Hammer.READY = false; + +/** + * setup events to detect gestures on the document + */ +function setup() { + if(Hammer.READY) { + return; + } + + // find what eventtypes we add listeners to + Hammer.event.determineEventTypes(); + + // Register all gestures inside Hammer.gestures + for(var name in Hammer.gestures) { + if(Hammer.gestures.hasOwnProperty(name)) { + Hammer.detection.register(Hammer.gestures[name]); + } + } + + // Add touch events on the document + Hammer.event.onTouch(Hammer.DOCUMENT, Hammer.EVENT_MOVE, Hammer.detection.detect); + Hammer.event.onTouch(Hammer.DOCUMENT, Hammer.EVENT_END, Hammer.detection.detect); + + // Hammer is ready...! + Hammer.READY = true; +} + +/** + * create new hammer instance + * all methods should return the instance itself, so it is chainable. + * @param {HTMLElement} element + * @param {Object} [options={}] + * @returns {Hammer.Instance} + * @constructor + */ +Hammer.Instance = function(element, options) { + var self = this; + + // setup HammerJS window events and register all gestures + // this also sets up the default options + setup(); + + this.element = element; + + // start/stop detection option + this.enabled = true; + + // merge options + this.options = Hammer.utils.extend( + Hammer.utils.extend({}, Hammer.defaults), + options || {}); + + // add some css to the element to prevent the browser from doing its native behavoir + if(this.options.stop_browser_behavior) { + Hammer.utils.stopDefaultBrowserBehavior(this.element, this.options.stop_browser_behavior); + } + + // start detection on touchstart + Hammer.event.onTouch(element, Hammer.EVENT_START, function(ev) { + if(self.enabled) { + Hammer.detection.startDetect(self, ev); + } + }); + + // return instance + return this; +}; + + +Hammer.Instance.prototype = { + /** + * bind events to the instance + * @param {String} gesture + * @param {Function} handler + * @returns {Hammer.Instance} + */ + on: function onEvent(gesture, handler){ + var gestures = gesture.split(' '); + for(var t=0; t 0 && eventType == Hammer.EVENT_END) { + eventType = Hammer.EVENT_MOVE; + } + // no touches, force the end event + else if(!count_touches) { + eventType = Hammer.EVENT_END; + } + + // because touchend has no touches, and we often want to use these in our gestures, + // we send the last move event as our eventData in touchend + if(!count_touches && last_move_event !== null) { + ev = last_move_event; + } + // store the last move event + else { + last_move_event = ev; + } + + // trigger the handler + handler.call(Hammer.detection, self.collectEventData(element, eventType, ev)); + + // remove pointerevent from list + if(Hammer.HAS_POINTEREVENTS && eventType == Hammer.EVENT_END) { + count_touches = Hammer.PointerEvent.updatePointer(eventType, ev); + } + } + + //debug(sourceEventType +" "+ eventType); + + // on the end we reset everything + if(!count_touches) { + last_move_event = null; + enable_detect = false; + touch_triggered = false; + Hammer.PointerEvent.reset(); + } + }); + }, + + + /** + * we have different events for each device/browser + * determine what we need and set them in the Hammer.EVENT_TYPES constant + */ + determineEventTypes: function determineEventTypes() { + // determine the eventtype we want to set + var types; + + // pointerEvents magic + if(Hammer.HAS_POINTEREVENTS) { + types = Hammer.PointerEvent.getEvents(); + } + // on Android, iOS, blackberry, windows mobile we dont want any mouseevents + else if(Hammer.NO_MOUSEEVENTS) { + types = [ + 'touchstart', + 'touchmove', + 'touchend touchcancel']; + } + // for non pointer events browsers and mixed browsers, + // like chrome on windows8 touch laptop + else { + types = [ + 'touchstart mousedown', + 'touchmove mousemove', + 'touchend touchcancel mouseup']; + } + + Hammer.EVENT_TYPES[Hammer.EVENT_START] = types[0]; + Hammer.EVENT_TYPES[Hammer.EVENT_MOVE] = types[1]; + Hammer.EVENT_TYPES[Hammer.EVENT_END] = types[2]; + }, + + + /** + * create touchlist depending on the event + * @param {Object} ev + * @param {String} eventType used by the fakemultitouch plugin + */ + getTouchList: function getTouchList(ev/*, eventType*/) { + // get the fake pointerEvent touchlist + if(Hammer.HAS_POINTEREVENTS) { + return Hammer.PointerEvent.getTouchList(); + } + // get the touchlist + else if(ev.touches) { + return ev.touches; + } + // make fake touchlist from mouse position + else { + return [{ + identifier: 1, + pageX: ev.pageX, + pageY: ev.pageY, + target: ev.target + }]; + } + }, + + + /** + * collect event data for Hammer js + * @param {HTMLElement} element + * @param {String} eventType like Hammer.EVENT_MOVE + * @param {Object} eventData + */ + collectEventData: function collectEventData(element, eventType, ev) { + var touches = this.getTouchList(ev, eventType); + + // find out pointerType + var pointerType = Hammer.POINTER_TOUCH; + if(ev.type.match(/mouse/) || Hammer.PointerEvent.matchType(Hammer.POINTER_MOUSE, ev)) { + pointerType = Hammer.POINTER_MOUSE; + } + + return { + center : Hammer.utils.getCenter(touches), + timeStamp : new Date().getTime(), + target : ev.target, + touches : touches, + eventType : eventType, + pointerType : pointerType, + srcEvent : ev, + + /** + * prevent the browser default actions + * mostly used to disable scrolling of the browser + */ + preventDefault: function() { + if(this.srcEvent.preventManipulation) { + this.srcEvent.preventManipulation(); + } + + if(this.srcEvent.preventDefault) { + this.srcEvent.preventDefault(); + } + }, + + /** + * stop bubbling the event up to its parents + */ + stopPropagation: function() { + this.srcEvent.stopPropagation(); + }, + + /** + * immediately stop gesture detection + * might be useful after a swipe was detected + * @return {*} + */ + stopDetect: function() { + return Hammer.detection.stopDetect(); + } + }; + } +}; + +Hammer.PointerEvent = { + /** + * holds all pointers + * @type {Object} + */ + pointers: {}, + + /** + * get a list of pointers + * @returns {Array} touchlist + */ + getTouchList: function() { + var self = this; + var touchlist = []; + + // we can use forEach since pointerEvents only is in IE10 + Object.keys(self.pointers).sort().forEach(function(id) { + touchlist.push(self.pointers[id]); + }); + return touchlist; + }, + + /** + * update the position of a pointer + * @param {String} type Hammer.EVENT_END + * @param {Object} pointerEvent + */ + updatePointer: function(type, pointerEvent) { + if(type == Hammer.EVENT_END) { + this.pointers = {}; + } + else { + pointerEvent.identifier = pointerEvent.pointerId; + this.pointers[pointerEvent.pointerId] = pointerEvent; + } + + return Object.keys(this.pointers).length; + }, + + /** + * check if ev matches pointertype + * @param {String} pointerType Hammer.POINTER_MOUSE + * @param {PointerEvent} ev + */ + matchType: function(pointerType, ev) { + if(!ev.pointerType) { + return false; + } + + var types = {}; + types[Hammer.POINTER_MOUSE] = (ev.pointerType == ev.MSPOINTER_TYPE_MOUSE || ev.pointerType == Hammer.POINTER_MOUSE); + types[Hammer.POINTER_TOUCH] = (ev.pointerType == ev.MSPOINTER_TYPE_TOUCH || ev.pointerType == Hammer.POINTER_TOUCH); + types[Hammer.POINTER_PEN] = (ev.pointerType == ev.MSPOINTER_TYPE_PEN || ev.pointerType == Hammer.POINTER_PEN); + return types[pointerType]; + }, + + + /** + * get events + */ + getEvents: function() { + return [ + 'pointerdown MSPointerDown', + 'pointermove MSPointerMove', + 'pointerup pointercancel MSPointerUp MSPointerCancel' + ]; + }, + + /** + * reset the list + */ + reset: function() { + this.pointers = {}; + } +}; + + +Hammer.utils = { + /** + * extend method, + * also used for cloning when dest is an empty object + * @param {Object} dest + * @param {Object} src + * @parm {Boolean} merge do a merge + * @returns {Object} dest + */ + extend: function extend(dest, src, merge) { + for (var key in src) { + if(dest[key] !== undefined && merge) { + continue; + } + dest[key] = src[key]; + } + return dest; + }, + + + /** + * find if a node is in the given parent + * used for event delegation tricks + * @param {HTMLElement} node + * @param {HTMLElement} parent + * @returns {boolean} has_parent + */ + hasParent: function(node, parent) { + while(node){ + if(node == parent) { + return true; + } + node = node.parentNode; + } + return false; + }, + + + /** + * get the center of all the touches + * @param {Array} touches + * @returns {Object} center + */ + getCenter: function getCenter(touches) { + var valuesX = [], valuesY = []; + + for(var t= 0,len=touches.length; t= y) { + return touch1.pageX - touch2.pageX > 0 ? Hammer.DIRECTION_LEFT : Hammer.DIRECTION_RIGHT; + } + else { + return touch1.pageY - touch2.pageY > 0 ? Hammer.DIRECTION_UP : Hammer.DIRECTION_DOWN; + } + }, + + + /** + * calculate the distance between two touches + * @param {Touch} touch1 + * @param {Touch} touch2 + * @returns {Number} distance + */ + getDistance: function getDistance(touch1, touch2) { + var x = touch2.pageX - touch1.pageX, + y = touch2.pageY - touch1.pageY; + return Math.sqrt((x*x) + (y*y)); + }, + + + /** + * calculate the scale factor between two touchLists (fingers) + * no scale is 1, and goes down to 0 when pinched together, and bigger when pinched out + * @param {Array} start + * @param {Array} end + * @returns {Number} scale + */ + getScale: function getScale(start, end) { + // need two fingers... + if(start.length >= 2 && end.length >= 2) { + return this.getDistance(end[0], end[1]) / + this.getDistance(start[0], start[1]); + } + return 1; + }, + + + /** + * calculate the rotation degrees between two touchLists (fingers) + * @param {Array} start + * @param {Array} end + * @returns {Number} rotation + */ + getRotation: function getRotation(start, end) { + // need two fingers + if(start.length >= 2 && end.length >= 2) { + return this.getAngle(end[1], end[0]) - + this.getAngle(start[1], start[0]); + } + return 0; + }, + + + /** + * boolean if the direction is vertical + * @param {String} direction + * @returns {Boolean} is_vertical + */ + isVertical: function isVertical(direction) { + return (direction == Hammer.DIRECTION_UP || direction == Hammer.DIRECTION_DOWN); + }, + + + /** + * stop browser default behavior with css props + * @param {HtmlElement} element + * @param {Object} css_props + */ + stopDefaultBrowserBehavior: function stopDefaultBrowserBehavior(element, css_props) { + var prop, + vendors = ['webkit','khtml','moz','ms','o','']; + + if(!css_props || !element.style) { + return; + } + + // with css properties for modern browsers + for(var i = 0; i < vendors.length; i++) { + for(var p in css_props) { + if(css_props.hasOwnProperty(p)) { + prop = p; + + // vender prefix at the property + if(vendors[i]) { + prop = vendors[i] + prop.substring(0, 1).toUpperCase() + prop.substring(1); + } + + // set the style + element.style[prop] = css_props[p]; + } + } + } + + // also the disable onselectstart + if(css_props.userSelect == 'none') { + element.onselectstart = function() { + return false; + }; + } + } +}; + +Hammer.detection = { + // contains all registred Hammer.gestures in the correct order + gestures: [], + + // data of the current Hammer.gesture detection session + current: null, + + // the previous Hammer.gesture session data + // is a full clone of the previous gesture.current object + previous: null, + + // when this becomes true, no gestures are fired + stopped: false, + + + /** + * start Hammer.gesture detection + * @param {Hammer.Instance} inst + * @param {Object} eventData + */ + startDetect: function startDetect(inst, eventData) { + // already busy with a Hammer.gesture detection on an element + if(this.current) { + return; + } + + this.stopped = false; + + this.current = { + inst : inst, // reference to HammerInstance we're working for + startEvent : Hammer.utils.extend({}, eventData), // start eventData for distances, timing etc + lastEvent : false, // last eventData + name : '' // current gesture we're in/detected, can be 'tap', 'hold' etc + }; + + this.detect(eventData); + }, + + + /** + * Hammer.gesture detection + * @param {Object} eventData + * @param {Object} eventData + */ + detect: function detect(eventData) { + if(!this.current || this.stopped) { + return; + } + + // extend event data with calculations about scale, distance etc + eventData = this.extendEventData(eventData); + + // instance options + var inst_options = this.current.inst.options; + + // call Hammer.gesture handlers + for(var g=0,len=this.gestures.length; g b.index) { + return 1; + } + return 0; + }); + + return this.gestures; + } +}; + + +Hammer.gestures = Hammer.gestures || {}; + +/** + * Custom gestures + * ============================== + * + * Gesture object + * -------------------- + * The object structure of a gesture: + * + * { name: 'mygesture', + * index: 1337, + * defaults: { + * mygesture_option: true + * } + * handler: function(type, ev, inst) { + * // trigger gesture event + * inst.trigger(this.name, ev); + * } + * } + + * @param {String} name + * this should be the name of the gesture, lowercase + * it is also being used to disable/enable the gesture per instance config. + * + * @param {Number} [index=1000] + * the index of the gesture, where it is going to be in the stack of gestures detection + * like when you build an gesture that depends on the drag gesture, it is a good + * idea to place it after the index of the drag gesture. + * + * @param {Object} [defaults={}] + * the default settings of the gesture. these are added to the instance settings, + * and can be overruled per instance. you can also add the name of the gesture, + * but this is also added by default (and set to true). + * + * @param {Function} handler + * this handles the gesture detection of your custom gesture and receives the + * following arguments: + * + * @param {Object} eventData + * event data containing the following properties: + * timeStamp {Number} time the event occurred + * target {HTMLElement} target element + * touches {Array} touches (fingers, pointers, mouse) on the screen + * pointerType {String} kind of pointer that was used. matches Hammer.POINTER_MOUSE|TOUCH + * center {Object} center position of the touches. contains pageX and pageY + * deltaTime {Number} the total time of the touches in the screen + * deltaX {Number} the delta on x axis we haved moved + * deltaY {Number} the delta on y axis we haved moved + * velocityX {Number} the velocity on the x + * velocityY {Number} the velocity on y + * angle {Number} the angle we are moving + * direction {String} the direction we are moving. matches Hammer.DIRECTION_UP|DOWN|LEFT|RIGHT + * distance {Number} the distance we haved moved + * scale {Number} scaling of the touches, needs 2 touches + * rotation {Number} rotation of the touches, needs 2 touches * + * eventType {String} matches Hammer.EVENT_START|MOVE|END + * srcEvent {Object} the source event, like TouchStart or MouseDown * + * startEvent {Object} contains the same properties as above, + * but from the first touch. this is used to calculate + * distances, deltaTime, scaling etc + * + * @param {Hammer.Instance} inst + * the instance we are doing the detection for. you can get the options from + * the inst.options object and trigger the gesture event by calling inst.trigger + * + * + * Handle gestures + * -------------------- + * inside the handler you can get/set Hammer.detection.current. This is the current + * detection session. It has the following properties + * @param {String} name + * contains the name of the gesture we have detected. it has not a real function, + * only to check in other gestures if something is detected. + * like in the drag gesture we set it to 'drag' and in the swipe gesture we can + * check if the current gesture is 'drag' by accessing Hammer.detection.current.name + * + * @readonly + * @param {Hammer.Instance} inst + * the instance we do the detection for + * + * @readonly + * @param {Object} startEvent + * contains the properties of the first gesture detection in this session. + * Used for calculations about timing, distance, etc. + * + * @readonly + * @param {Object} lastEvent + * contains all the properties of the last gesture detect in this session. + * + * after the gesture detection session has been completed (user has released the screen) + * the Hammer.detection.current object is copied into Hammer.detection.previous, + * this is usefull for gestures like doubletap, where you need to know if the + * previous gesture was a tap + * + * options that have been set by the instance can be received by calling inst.options + * + * You can trigger a gesture event by calling inst.trigger("mygesture", event). + * The first param is the name of your gesture, the second the event argument + * + * + * Register gestures + * -------------------- + * When an gesture is added to the Hammer.gestures object, it is auto registered + * at the setup of the first Hammer instance. You can also call Hammer.detection.register + * manually and pass your gesture object as a param + * + */ + +/** + * Hold + * Touch stays at the same place for x time + * @events hold + */ +Hammer.gestures.Hold = { + name: 'hold', + index: 10, + defaults: { + hold_timeout : 500, + hold_threshold : 1 + }, + timer: null, + handler: function holdGesture(ev, inst) { + switch(ev.eventType) { + case Hammer.EVENT_START: + // clear any running timers + clearTimeout(this.timer); + + // set the gesture so we can check in the timeout if it still is + Hammer.detection.current.name = this.name; + + // set timer and if after the timeout it still is hold, + // we trigger the hold event + this.timer = setTimeout(function() { + if(Hammer.detection.current.name == 'hold') { + inst.trigger('hold', ev); + } + }, inst.options.hold_timeout); + break; + + // when you move or end we clear the timer + case Hammer.EVENT_MOVE: + if(ev.distance > inst.options.hold_threshold) { + clearTimeout(this.timer); + } + break; + + case Hammer.EVENT_END: + clearTimeout(this.timer); + break; + } + } +}; + + +/** + * Tap/DoubleTap + * Quick touch at a place or double at the same place + * @events tap, doubletap + */ +Hammer.gestures.Tap = { + name: 'tap', + index: 100, + defaults: { + tap_max_touchtime : 250, + tap_max_distance : 10, + tap_always : true, + doubletap_distance : 20, + doubletap_interval : 300 + }, + handler: function tapGesture(ev, inst) { + if(ev.eventType == Hammer.EVENT_END) { + // previous gesture, for the double tap since these are two different gesture detections + var prev = Hammer.detection.previous, + did_doubletap = false; + + // when the touchtime is higher then the max touch time + // or when the moving distance is too much + if(ev.deltaTime > inst.options.tap_max_touchtime || + ev.distance > inst.options.tap_max_distance) { + return; + } + + // check if double tap + if(prev && prev.name == 'tap' && + (ev.timeStamp - prev.lastEvent.timeStamp) < inst.options.doubletap_interval && + ev.distance < inst.options.doubletap_distance) { + inst.trigger('doubletap', ev); + did_doubletap = true; + } + + // do a single tap + if(!did_doubletap || inst.options.tap_always) { + Hammer.detection.current.name = 'tap'; + inst.trigger(Hammer.detection.current.name, ev); + } + } + } +}; + + +/** + * Swipe + * triggers swipe events when the end velocity is above the threshold + * @events swipe, swipeleft, swiperight, swipeup, swipedown + */ +Hammer.gestures.Swipe = { + name: 'swipe', + index: 40, + defaults: { + // set 0 for unlimited, but this can conflict with transform + swipe_max_touches : 1, + swipe_velocity : 0.7 + }, + handler: function swipeGesture(ev, inst) { + if(ev.eventType == Hammer.EVENT_END) { + // max touches + if(inst.options.swipe_max_touches > 0 && + ev.touches.length > inst.options.swipe_max_touches) { + return; + } + + // when the distance we moved is too small we skip this gesture + // or we can be already in dragging + if(ev.velocityX > inst.options.swipe_velocity || + ev.velocityY > inst.options.swipe_velocity) { + // trigger swipe events + inst.trigger(this.name, ev); + inst.trigger(this.name + ev.direction, ev); + } + } + } +}; + + +/** + * Drag + * Move with x fingers (default 1) around on the page. Blocking the scrolling when + * moving left and right is a good practice. When all the drag events are blocking + * you disable scrolling on that area. + * @events drag, drapleft, dragright, dragup, dragdown + */ +Hammer.gestures.Drag = { + name: 'drag', + index: 50, + defaults: { + drag_min_distance : 10, + // set 0 for unlimited, but this can conflict with transform + drag_max_touches : 1, + // prevent default browser behavior when dragging occurs + // be careful with it, it makes the element a blocking element + // when you are using the drag gesture, it is a good practice to set this true + drag_block_horizontal : false, + drag_block_vertical : false, + // drag_lock_to_axis keeps the drag gesture on the axis that it started on, + // It disallows vertical directions if the initial direction was horizontal, and vice versa. + drag_lock_to_axis : false, + // drag lock only kicks in when distance > drag_lock_min_distance + // This way, locking occurs only when the distance has become large enough to reliably determine the direction + drag_lock_min_distance : 25 + }, + triggered: false, + handler: function dragGesture(ev, inst) { + // current gesture isnt drag, but dragged is true + // this means an other gesture is busy. now call dragend + if(Hammer.detection.current.name != this.name && this.triggered) { + inst.trigger(this.name +'end', ev); + this.triggered = false; + return; + } + + // max touches + if(inst.options.drag_max_touches > 0 && + ev.touches.length > inst.options.drag_max_touches) { + return; + } + + switch(ev.eventType) { + case Hammer.EVENT_START: + this.triggered = false; + break; + + case Hammer.EVENT_MOVE: + // when the distance we moved is too small we skip this gesture + // or we can be already in dragging + if(ev.distance < inst.options.drag_min_distance && + Hammer.detection.current.name != this.name) { + return; + } + + // we are dragging! + Hammer.detection.current.name = this.name; + + // lock drag to axis? + if(Hammer.detection.current.lastEvent.drag_locked_to_axis || (inst.options.drag_lock_to_axis && inst.options.drag_lock_min_distance<=ev.distance)) { + ev.drag_locked_to_axis = true; + } + var last_direction = Hammer.detection.current.lastEvent.direction; + if(ev.drag_locked_to_axis && last_direction !== ev.direction) { + // keep direction on the axis that the drag gesture started on + if(Hammer.utils.isVertical(last_direction)) { + ev.direction = (ev.deltaY < 0) ? Hammer.DIRECTION_UP : Hammer.DIRECTION_DOWN; + } + else { + ev.direction = (ev.deltaX < 0) ? Hammer.DIRECTION_LEFT : Hammer.DIRECTION_RIGHT; + } + } + + // first time, trigger dragstart event + if(!this.triggered) { + inst.trigger(this.name +'start', ev); + this.triggered = true; + } + + // trigger normal event + inst.trigger(this.name, ev); + + // direction event, like dragdown + inst.trigger(this.name + ev.direction, ev); + + // block the browser events + if( (inst.options.drag_block_vertical && Hammer.utils.isVertical(ev.direction)) || + (inst.options.drag_block_horizontal && !Hammer.utils.isVertical(ev.direction))) { + ev.preventDefault(); + } + break; + + case Hammer.EVENT_END: + // trigger dragend + if(this.triggered) { + inst.trigger(this.name +'end', ev); + } + + this.triggered = false; + break; + } + } +}; + + +/** + * Transform + * User want to scale or rotate with 2 fingers + * @events transform, pinch, pinchin, pinchout, rotate + */ +Hammer.gestures.Transform = { + name: 'transform', + index: 45, + defaults: { + // factor, no scale is 1, zoomin is to 0 and zoomout until higher then 1 + transform_min_scale : 0.01, + // rotation in degrees + transform_min_rotation : 1, + // prevent default browser behavior when two touches are on the screen + // but it makes the element a blocking element + // when you are using the transform gesture, it is a good practice to set this true + transform_always_block : false + }, + triggered: false, + handler: function transformGesture(ev, inst) { + // current gesture isnt drag, but dragged is true + // this means an other gesture is busy. now call dragend + if(Hammer.detection.current.name != this.name && this.triggered) { + inst.trigger(this.name +'end', ev); + this.triggered = false; + return; + } + + // atleast multitouch + if(ev.touches.length < 2) { + return; + } + + // prevent default when two fingers are on the screen + if(inst.options.transform_always_block) { + ev.preventDefault(); + } + + switch(ev.eventType) { + case Hammer.EVENT_START: + this.triggered = false; + break; + + case Hammer.EVENT_MOVE: + var scale_threshold = Math.abs(1-ev.scale); + var rotation_threshold = Math.abs(ev.rotation); + + // when the distance we moved is too small we skip this gesture + // or we can be already in dragging + if(scale_threshold < inst.options.transform_min_scale && + rotation_threshold < inst.options.transform_min_rotation) { + return; + } + + // we are transforming! + Hammer.detection.current.name = this.name; + + // first time, trigger dragstart event + if(!this.triggered) { + inst.trigger(this.name +'start', ev); + this.triggered = true; + } + + inst.trigger(this.name, ev); // basic transform event + + // trigger rotate event + if(rotation_threshold > inst.options.transform_min_rotation) { + inst.trigger('rotate', ev); + } + + // trigger pinch event + if(scale_threshold > inst.options.transform_min_scale) { + inst.trigger('pinch', ev); + inst.trigger('pinch'+ ((ev.scale < 1) ? 'in' : 'out'), ev); + } + break; + + case Hammer.EVENT_END: + // trigger dragend + if(this.triggered) { + inst.trigger(this.name +'end', ev); + } + + this.triggered = false; + break; + } + } +}; + + +/** + * Touch + * Called as first, tells the user has touched the screen + * @events touch + */ +Hammer.gestures.Touch = { + name: 'touch', + index: -Infinity, + defaults: { + // call preventDefault at touchstart, and makes the element blocking by + // disabling the scrolling of the page, but it improves gestures like + // transforming and dragging. + // be careful with using this, it can be very annoying for users to be stuck + // on the page + prevent_default: false, + + // disable mouse events, so only touch (or pen!) input triggers events + prevent_mouseevents: false + }, + handler: function touchGesture(ev, inst) { + if(inst.options.prevent_mouseevents && ev.pointerType == Hammer.POINTER_MOUSE) { + ev.stopDetect(); + return; + } + + if(inst.options.prevent_default) { + ev.preventDefault(); + } + + if(ev.eventType == Hammer.EVENT_START) { + inst.trigger(this.name, ev); + } + } +}; + + +/** + * Release + * Called as last, tells the user has released the screen + * @events release + */ +Hammer.gestures.Release = { + name: 'release', + index: Infinity, + handler: function releaseGesture(ev, inst) { + if(ev.eventType == Hammer.EVENT_END) { + inst.trigger(this.name, ev); + } + } +}; + +// node export +if(typeof module === 'object' && typeof module.exports === 'object'){ + module.exports = Hammer; +} +// just window export +else { + window.Hammer = Hammer; + + // requireJS module definition + if(typeof window.define === 'function' && window.define.amd) { + window.define('hammer', [], function() { + return Hammer; + }); + } +} +})(this); +},{}],4:[function(require,module,exports){ +//! moment.js +//! version : 2.5.1 +//! authors : Tim Wood, Iskren Chernev, Moment.js contributors +//! license : MIT +//! momentjs.com + +(function (undefined) { + + /************************************ + Constants + ************************************/ + + var moment, + VERSION = "2.5.1", + global = this, + round = Math.round, + i, + + YEAR = 0, + MONTH = 1, + DATE = 2, + HOUR = 3, + MINUTE = 4, + SECOND = 5, + MILLISECOND = 6, + + // internal storage for language config files + languages = {}, + + // moment internal properties + momentProperties = { + _isAMomentObject: null, + _i : null, + _f : null, + _l : null, + _strict : null, + _isUTC : null, + _offset : null, // optional. Combine with _isUTC + _pf : null, + _lang : null // optional + }, + + // check for nodeJS + hasModule = (typeof module !== 'undefined' && module.exports && typeof require !== 'undefined'), + + // ASP.NET json date format regex + aspNetJsonRegex = /^\/?Date\((\-?\d+)/i, + aspNetTimeSpanJsonRegex = /(\-)?(?:(\d*)\.)?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?)?/, + + // from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html + // somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere + isoDurationRegex = /^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/, + + // format tokens + formattingTokens = /(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,4}|X|zz?|ZZ?|.)/g, + localFormattingTokens = /(\[[^\[]*\])|(\\)?(LT|LL?L?L?|l{1,4})/g, + + // parsing token regexes + parseTokenOneOrTwoDigits = /\d\d?/, // 0 - 99 + parseTokenOneToThreeDigits = /\d{1,3}/, // 0 - 999 + parseTokenOneToFourDigits = /\d{1,4}/, // 0 - 9999 + parseTokenOneToSixDigits = /[+\-]?\d{1,6}/, // -999,999 - 999,999 + parseTokenDigits = /\d+/, // nonzero number of digits + parseTokenWord = /[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i, // any word (or two) characters or numbers including two/three word month in arabic. + parseTokenTimezone = /Z|[\+\-]\d\d:?\d\d/gi, // +00:00 -00:00 +0000 -0000 or Z + parseTokenT = /T/i, // T (ISO separator) + parseTokenTimestampMs = /[\+\-]?\d+(\.\d{1,3})?/, // 123456789 123456789.123 + + //strict parsing regexes + parseTokenOneDigit = /\d/, // 0 - 9 + parseTokenTwoDigits = /\d\d/, // 00 - 99 + parseTokenThreeDigits = /\d{3}/, // 000 - 999 + parseTokenFourDigits = /\d{4}/, // 0000 - 9999 + parseTokenSixDigits = /[+-]?\d{6}/, // -999,999 - 999,999 + parseTokenSignedNumber = /[+-]?\d+/, // -inf - inf + + // iso 8601 regex + // 0000-00-00 0000-W00 or 0000-W00-0 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000 or +00) + isoRegex = /^\s*(?:[+-]\d{6}|\d{4})-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/, + + isoFormat = 'YYYY-MM-DDTHH:mm:ssZ', + + isoDates = [ + ['YYYYYY-MM-DD', /[+-]\d{6}-\d{2}-\d{2}/], + ['YYYY-MM-DD', /\d{4}-\d{2}-\d{2}/], + ['GGGG-[W]WW-E', /\d{4}-W\d{2}-\d/], + ['GGGG-[W]WW', /\d{4}-W\d{2}/], + ['YYYY-DDD', /\d{4}-\d{3}/] + ], + + // iso time formats and regexes + isoTimes = [ + ['HH:mm:ss.SSSS', /(T| )\d\d:\d\d:\d\d\.\d{1,3}/], + ['HH:mm:ss', /(T| )\d\d:\d\d:\d\d/], + ['HH:mm', /(T| )\d\d:\d\d/], + ['HH', /(T| )\d\d/] + ], + + // timezone chunker "+10:00" > ["10", "00"] or "-1530" > ["-15", "30"] + parseTimezoneChunker = /([\+\-]|\d\d)/gi, + + // getter and setter names + proxyGettersAndSetters = 'Date|Hours|Minutes|Seconds|Milliseconds'.split('|'), + unitMillisecondFactors = { + 'Milliseconds' : 1, + 'Seconds' : 1e3, + 'Minutes' : 6e4, + 'Hours' : 36e5, + 'Days' : 864e5, + 'Months' : 2592e6, + 'Years' : 31536e6 + }, + + unitAliases = { + ms : 'millisecond', + s : 'second', + m : 'minute', + h : 'hour', + d : 'day', + D : 'date', + w : 'week', + W : 'isoWeek', + M : 'month', + y : 'year', + DDD : 'dayOfYear', + e : 'weekday', + E : 'isoWeekday', + gg: 'weekYear', + GG: 'isoWeekYear' + }, + + camelFunctions = { + dayofyear : 'dayOfYear', + isoweekday : 'isoWeekday', + isoweek : 'isoWeek', + weekyear : 'weekYear', + isoweekyear : 'isoWeekYear' + }, + + // format function strings + formatFunctions = {}, + + // tokens to ordinalize and pad + ordinalizeTokens = 'DDD w W M D d'.split(' '), + paddedTokens = 'M D H h m s w W'.split(' '), + + formatTokenFunctions = { + M : function () { + return this.month() + 1; + }, + MMM : function (format) { + return this.lang().monthsShort(this, format); + }, + MMMM : function (format) { + return this.lang().months(this, format); + }, + D : function () { + return this.date(); + }, + DDD : function () { + return this.dayOfYear(); + }, + d : function () { + return this.day(); + }, + dd : function (format) { + return this.lang().weekdaysMin(this, format); + }, + ddd : function (format) { + return this.lang().weekdaysShort(this, format); + }, + dddd : function (format) { + return this.lang().weekdays(this, format); + }, + w : function () { + return this.week(); + }, + W : function () { + return this.isoWeek(); + }, + YY : function () { + return leftZeroFill(this.year() % 100, 2); + }, + YYYY : function () { + return leftZeroFill(this.year(), 4); + }, + YYYYY : function () { + return leftZeroFill(this.year(), 5); + }, + YYYYYY : function () { + var y = this.year(), sign = y >= 0 ? '+' : '-'; + return sign + leftZeroFill(Math.abs(y), 6); + }, + gg : function () { + return leftZeroFill(this.weekYear() % 100, 2); + }, + gggg : function () { + return leftZeroFill(this.weekYear(), 4); + }, + ggggg : function () { + return leftZeroFill(this.weekYear(), 5); + }, + GG : function () { + return leftZeroFill(this.isoWeekYear() % 100, 2); + }, + GGGG : function () { + return leftZeroFill(this.isoWeekYear(), 4); + }, + GGGGG : function () { + return leftZeroFill(this.isoWeekYear(), 5); + }, + e : function () { + return this.weekday(); + }, + E : function () { + return this.isoWeekday(); + }, + a : function () { + return this.lang().meridiem(this.hours(), this.minutes(), true); + }, + A : function () { + return this.lang().meridiem(this.hours(), this.minutes(), false); + }, + H : function () { + return this.hours(); + }, + h : function () { + return this.hours() % 12 || 12; + }, + m : function () { + return this.minutes(); + }, + s : function () { + return this.seconds(); + }, + S : function () { + return toInt(this.milliseconds() / 100); + }, + SS : function () { + return leftZeroFill(toInt(this.milliseconds() / 10), 2); + }, + SSS : function () { + return leftZeroFill(this.milliseconds(), 3); + }, + SSSS : function () { + return leftZeroFill(this.milliseconds(), 3); + }, + Z : function () { + var a = -this.zone(), + b = "+"; + if (a < 0) { + a = -a; + b = "-"; + } + return b + leftZeroFill(toInt(a / 60), 2) + ":" + leftZeroFill(toInt(a) % 60, 2); + }, + ZZ : function () { + var a = -this.zone(), + b = "+"; + if (a < 0) { + a = -a; + b = "-"; + } + return b + leftZeroFill(toInt(a / 60), 2) + leftZeroFill(toInt(a) % 60, 2); + }, + z : function () { + return this.zoneAbbr(); + }, + zz : function () { + return this.zoneName(); + }, + X : function () { + return this.unix(); + }, + Q : function () { + return this.quarter(); + } + }, + + lists = ['months', 'monthsShort', 'weekdays', 'weekdaysShort', 'weekdaysMin']; + + function defaultParsingFlags() { + // We need to deep clone this object, and es5 standard is not very + // helpful. + return { + empty : false, + unusedTokens : [], + unusedInput : [], + overflow : -2, + charsLeftOver : 0, + nullInput : false, + invalidMonth : null, + invalidFormat : false, + userInvalidated : false, + iso: false + }; + } + + function padToken(func, count) { + return function (a) { + return leftZeroFill(func.call(this, a), count); + }; + } + function ordinalizeToken(func, period) { + return function (a) { + return this.lang().ordinal(func.call(this, a), period); + }; + } + + while (ordinalizeTokens.length) { + i = ordinalizeTokens.pop(); + formatTokenFunctions[i + 'o'] = ordinalizeToken(formatTokenFunctions[i], i); + } + while (paddedTokens.length) { + i = paddedTokens.pop(); + formatTokenFunctions[i + i] = padToken(formatTokenFunctions[i], 2); + } + formatTokenFunctions.DDDD = padToken(formatTokenFunctions.DDD, 3); + + + /************************************ + Constructors + ************************************/ + + function Language() { + + } + + // Moment prototype object + function Moment(config) { + checkOverflow(config); + extend(this, config); + } + + // Duration Constructor + function Duration(duration) { + var normalizedInput = normalizeObjectUnits(duration), + years = normalizedInput.year || 0, + months = normalizedInput.month || 0, + weeks = normalizedInput.week || 0, + days = normalizedInput.day || 0, + hours = normalizedInput.hour || 0, + minutes = normalizedInput.minute || 0, + seconds = normalizedInput.second || 0, + milliseconds = normalizedInput.millisecond || 0; + + // representation for dateAddRemove + this._milliseconds = +milliseconds + + seconds * 1e3 + // 1000 + minutes * 6e4 + // 1000 * 60 + hours * 36e5; // 1000 * 60 * 60 + // Because of dateAddRemove treats 24 hours as different from a + // day when working around DST, we need to store them separately + this._days = +days + + weeks * 7; + // It is impossible translate months into days without knowing + // which months you are are talking about, so we have to store + // it separately. + this._months = +months + + years * 12; + + this._data = {}; + + this._bubble(); + } + + /************************************ + Helpers + ************************************/ + + + function extend(a, b) { + for (var i in b) { + if (b.hasOwnProperty(i)) { + a[i] = b[i]; + } + } + + if (b.hasOwnProperty("toString")) { + a.toString = b.toString; + } + + if (b.hasOwnProperty("valueOf")) { + a.valueOf = b.valueOf; + } + + return a; + } + + function cloneMoment(m) { + var result = {}, i; + for (i in m) { + if (m.hasOwnProperty(i) && momentProperties.hasOwnProperty(i)) { + result[i] = m[i]; + } + } + + return result; + } + + function absRound(number) { + if (number < 0) { + return Math.ceil(number); + } else { + return Math.floor(number); + } + } + + // left zero fill a number + // see http://jsperf.com/left-zero-filling for performance comparison + function leftZeroFill(number, targetLength, forceSign) { + var output = '' + Math.abs(number), + sign = number >= 0; + + while (output.length < targetLength) { + output = '0' + output; + } + return (sign ? (forceSign ? '+' : '') : '-') + output; + } + + // helper function for _.addTime and _.subtractTime + function addOrSubtractDurationFromMoment(mom, duration, isAdding, ignoreUpdateOffset) { + var milliseconds = duration._milliseconds, + days = duration._days, + months = duration._months, + minutes, + hours; + + if (milliseconds) { + mom._d.setTime(+mom._d + milliseconds * isAdding); + } + // store the minutes and hours so we can restore them + if (days || months) { + minutes = mom.minute(); + hours = mom.hour(); + } + if (days) { + mom.date(mom.date() + days * isAdding); + } + if (months) { + mom.month(mom.month() + months * isAdding); + } + if (milliseconds && !ignoreUpdateOffset) { + moment.updateOffset(mom); + } + // restore the minutes and hours after possibly changing dst + if (days || months) { + mom.minute(minutes); + mom.hour(hours); + } + } + + // check if is an array + function isArray(input) { + return Object.prototype.toString.call(input) === '[object Array]'; + } + + function isDate(input) { + return Object.prototype.toString.call(input) === '[object Date]' || + input instanceof Date; + } + + // compare two arrays, return the number of differences + function compareArrays(array1, array2, dontConvert) { + var len = Math.min(array1.length, array2.length), + lengthDiff = Math.abs(array1.length - array2.length), + diffs = 0, + i; + for (i = 0; i < len; i++) { + if ((dontConvert && array1[i] !== array2[i]) || + (!dontConvert && toInt(array1[i]) !== toInt(array2[i]))) { + diffs++; + } + } + return diffs + lengthDiff; + } + + function normalizeUnits(units) { + if (units) { + var lowered = units.toLowerCase().replace(/(.)s$/, '$1'); + units = unitAliases[units] || camelFunctions[lowered] || lowered; + } + return units; + } + + function normalizeObjectUnits(inputObject) { + var normalizedInput = {}, + normalizedProp, + prop; + + for (prop in inputObject) { + if (inputObject.hasOwnProperty(prop)) { + normalizedProp = normalizeUnits(prop); + if (normalizedProp) { + normalizedInput[normalizedProp] = inputObject[prop]; + } + } + } + + return normalizedInput; + } + + function makeList(field) { + var count, setter; + + if (field.indexOf('week') === 0) { + count = 7; + setter = 'day'; + } + else if (field.indexOf('month') === 0) { + count = 12; + setter = 'month'; + } + else { + return; + } + + moment[field] = function (format, index) { + var i, getter, + method = moment.fn._lang[field], + results = []; + + if (typeof format === 'number') { + index = format; + format = undefined; + } + + getter = function (i) { + var m = moment().utc().set(setter, i); + return method.call(moment.fn._lang, m, format || ''); + }; + + if (index != null) { + return getter(index); + } + else { + for (i = 0; i < count; i++) { + results.push(getter(i)); + } + return results; + } + }; + } + + function toInt(argumentForCoercion) { + var coercedNumber = +argumentForCoercion, + value = 0; + + if (coercedNumber !== 0 && isFinite(coercedNumber)) { + if (coercedNumber >= 0) { + value = Math.floor(coercedNumber); + } else { + value = Math.ceil(coercedNumber); + } + } + + return value; + } + + function daysInMonth(year, month) { + return new Date(Date.UTC(year, month + 1, 0)).getUTCDate(); + } + + function daysInYear(year) { + return isLeapYear(year) ? 366 : 365; + } + + function isLeapYear(year) { + return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0; + } + + function checkOverflow(m) { + var overflow; + if (m._a && m._pf.overflow === -2) { + overflow = + m._a[MONTH] < 0 || m._a[MONTH] > 11 ? MONTH : + m._a[DATE] < 1 || m._a[DATE] > daysInMonth(m._a[YEAR], m._a[MONTH]) ? DATE : + m._a[HOUR] < 0 || m._a[HOUR] > 23 ? HOUR : + m._a[MINUTE] < 0 || m._a[MINUTE] > 59 ? MINUTE : + m._a[SECOND] < 0 || m._a[SECOND] > 59 ? SECOND : + m._a[MILLISECOND] < 0 || m._a[MILLISECOND] > 999 ? MILLISECOND : + -1; + + if (m._pf._overflowDayOfYear && (overflow < YEAR || overflow > DATE)) { + overflow = DATE; + } + + m._pf.overflow = overflow; + } + } + + function isValid(m) { + if (m._isValid == null) { + m._isValid = !isNaN(m._d.getTime()) && + m._pf.overflow < 0 && + !m._pf.empty && + !m._pf.invalidMonth && + !m._pf.nullInput && + !m._pf.invalidFormat && + !m._pf.userInvalidated; + + if (m._strict) { + m._isValid = m._isValid && + m._pf.charsLeftOver === 0 && + m._pf.unusedTokens.length === 0; + } + } + return m._isValid; + } + + function normalizeLanguage(key) { + return key ? key.toLowerCase().replace('_', '-') : key; + } + + // Return a moment from input, that is local/utc/zone equivalent to model. + function makeAs(input, model) { + return model._isUTC ? moment(input).zone(model._offset || 0) : + moment(input).local(); + } + + /************************************ + Languages + ************************************/ + + + extend(Language.prototype, { + + set : function (config) { + var prop, i; + for (i in config) { + prop = config[i]; + if (typeof prop === 'function') { + this[i] = prop; + } else { + this['_' + i] = prop; + } + } + }, + + _months : "January_February_March_April_May_June_July_August_September_October_November_December".split("_"), + months : function (m) { + return this._months[m.month()]; + }, + + _monthsShort : "Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"), + monthsShort : function (m) { + return this._monthsShort[m.month()]; + }, + + monthsParse : function (monthName) { + var i, mom, regex; + + if (!this._monthsParse) { + this._monthsParse = []; + } + + for (i = 0; i < 12; i++) { + // make the regex if we don't have it already + if (!this._monthsParse[i]) { + mom = moment.utc([2000, i]); + regex = '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, ''); + this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i'); + } + // test the regex + if (this._monthsParse[i].test(monthName)) { + return i; + } + } + }, + + _weekdays : "Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"), + weekdays : function (m) { + return this._weekdays[m.day()]; + }, + + _weekdaysShort : "Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"), + weekdaysShort : function (m) { + return this._weekdaysShort[m.day()]; + }, + + _weekdaysMin : "Su_Mo_Tu_We_Th_Fr_Sa".split("_"), + weekdaysMin : function (m) { + return this._weekdaysMin[m.day()]; + }, + + weekdaysParse : function (weekdayName) { + var i, mom, regex; + + if (!this._weekdaysParse) { + this._weekdaysParse = []; + } + + for (i = 0; i < 7; i++) { + // make the regex if we don't have it already + if (!this._weekdaysParse[i]) { + mom = moment([2000, 1]).day(i); + regex = '^' + this.weekdays(mom, '') + '|^' + this.weekdaysShort(mom, '') + '|^' + this.weekdaysMin(mom, ''); + this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i'); + } + // test the regex + if (this._weekdaysParse[i].test(weekdayName)) { + return i; + } + } + }, + + _longDateFormat : { + LT : "h:mm A", + L : "MM/DD/YYYY", + LL : "MMMM D YYYY", + LLL : "MMMM D YYYY LT", + LLLL : "dddd, MMMM D YYYY LT" + }, + longDateFormat : function (key) { + var output = this._longDateFormat[key]; + if (!output && this._longDateFormat[key.toUpperCase()]) { + output = this._longDateFormat[key.toUpperCase()].replace(/MMMM|MM|DD|dddd/g, function (val) { + return val.slice(1); + }); + this._longDateFormat[key] = output; + } + return output; + }, + + isPM : function (input) { + // IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays + // Using charAt should be more compatible. + return ((input + '').toLowerCase().charAt(0) === 'p'); + }, + + _meridiemParse : /[ap]\.?m?\.?/i, + meridiem : function (hours, minutes, isLower) { + if (hours > 11) { + return isLower ? 'pm' : 'PM'; + } else { + return isLower ? 'am' : 'AM'; + } + }, + + _calendar : { + sameDay : '[Today at] LT', + nextDay : '[Tomorrow at] LT', + nextWeek : 'dddd [at] LT', + lastDay : '[Yesterday at] LT', + lastWeek : '[Last] dddd [at] LT', + sameElse : 'L' + }, + calendar : function (key, mom) { + var output = this._calendar[key]; + return typeof output === 'function' ? output.apply(mom) : output; + }, + + _relativeTime : { + future : "in %s", + past : "%s ago", + s : "a few seconds", + m : "a minute", + mm : "%d minutes", + h : "an hour", + hh : "%d hours", + d : "a day", + dd : "%d days", + M : "a month", + MM : "%d months", + y : "a year", + yy : "%d years" + }, + relativeTime : function (number, withoutSuffix, string, isFuture) { + var output = this._relativeTime[string]; + return (typeof output === 'function') ? + output(number, withoutSuffix, string, isFuture) : + output.replace(/%d/i, number); + }, + pastFuture : function (diff, output) { + var format = this._relativeTime[diff > 0 ? 'future' : 'past']; + return typeof format === 'function' ? format(output) : format.replace(/%s/i, output); + }, + + ordinal : function (number) { + return this._ordinal.replace("%d", number); + }, + _ordinal : "%d", + + preparse : function (string) { + return string; + }, + + postformat : function (string) { + return string; + }, + + week : function (mom) { + return weekOfYear(mom, this._week.dow, this._week.doy).week; + }, + + _week : { + dow : 0, // Sunday is the first day of the week. + doy : 6 // The week that contains Jan 1st is the first week of the year. + }, + + _invalidDate: 'Invalid date', + invalidDate: function () { + return this._invalidDate; + } + }); + + // Loads a language definition into the `languages` cache. The function + // takes a key and optionally values. If not in the browser and no values + // are provided, it will load the language file module. As a convenience, + // this function also returns the language values. + function loadLang(key, values) { + values.abbr = key; + if (!languages[key]) { + languages[key] = new Language(); + } + languages[key].set(values); + return languages[key]; + } + + // Remove a language from the `languages` cache. Mostly useful in tests. + function unloadLang(key) { + delete languages[key]; + } + + // Determines which language definition to use and returns it. + // + // With no parameters, it will return the global language. If you + // pass in a language key, such as 'en', it will return the + // definition for 'en', so long as 'en' has already been loaded using + // moment.lang. + function getLangDefinition(key) { + var i = 0, j, lang, next, split, + get = function (k) { + if (!languages[k] && hasModule) { + try { + require('./lang/' + k); + } catch (e) { } + } + return languages[k]; + }; + + if (!key) { + return moment.fn._lang; + } + + if (!isArray(key)) { + //short-circuit everything else + lang = get(key); + if (lang) { + return lang; + } + key = [key]; + } + + //pick the language from the array + //try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each + //substring from most specific to least, but move to the next array item if it's a more specific variant than the current root + while (i < key.length) { + split = normalizeLanguage(key[i]).split('-'); + j = split.length; + next = normalizeLanguage(key[i + 1]); + next = next ? next.split('-') : null; + while (j > 0) { + lang = get(split.slice(0, j).join('-')); + if (lang) { + return lang; + } + if (next && next.length >= j && compareArrays(split, next, true) >= j - 1) { + //the next array item is better than a shallower substring of this one + break; + } + j--; + } + i++; + } + return moment.fn._lang; + } + + /************************************ + Formatting + ************************************/ + + + function removeFormattingTokens(input) { + if (input.match(/\[[\s\S]/)) { + return input.replace(/^\[|\]$/g, ""); + } + return input.replace(/\\/g, ""); + } + + function makeFormatFunction(format) { + var array = format.match(formattingTokens), i, length; + + for (i = 0, length = array.length; i < length; i++) { + if (formatTokenFunctions[array[i]]) { + array[i] = formatTokenFunctions[array[i]]; + } else { + array[i] = removeFormattingTokens(array[i]); + } + } + + return function (mom) { + var output = ""; + for (i = 0; i < length; i++) { + output += array[i] instanceof Function ? array[i].call(mom, format) : array[i]; + } + return output; + }; + } + + // format date using native date object + function formatMoment(m, format) { + + if (!m.isValid()) { + return m.lang().invalidDate(); + } + + format = expandFormat(format, m.lang()); + + if (!formatFunctions[format]) { + formatFunctions[format] = makeFormatFunction(format); + } + + return formatFunctions[format](m); + } + + function expandFormat(format, lang) { + var i = 5; + + function replaceLongDateFormatTokens(input) { + return lang.longDateFormat(input) || input; + } + + localFormattingTokens.lastIndex = 0; + while (i >= 0 && localFormattingTokens.test(format)) { + format = format.replace(localFormattingTokens, replaceLongDateFormatTokens); + localFormattingTokens.lastIndex = 0; + i -= 1; + } + + return format; + } + + + /************************************ + Parsing + ************************************/ + + + // get the regex to find the next token + function getParseRegexForToken(token, config) { + var a, strict = config._strict; + switch (token) { + case 'DDDD': + return parseTokenThreeDigits; + case 'YYYY': + case 'GGGG': + case 'gggg': + return strict ? parseTokenFourDigits : parseTokenOneToFourDigits; + case 'Y': + case 'G': + case 'g': + return parseTokenSignedNumber; + case 'YYYYYY': + case 'YYYYY': + case 'GGGGG': + case 'ggggg': + return strict ? parseTokenSixDigits : parseTokenOneToSixDigits; + case 'S': + if (strict) { return parseTokenOneDigit; } + /* falls through */ + case 'SS': + if (strict) { return parseTokenTwoDigits; } + /* falls through */ + case 'SSS': + if (strict) { return parseTokenThreeDigits; } + /* falls through */ + case 'DDD': + return parseTokenOneToThreeDigits; + case 'MMM': + case 'MMMM': + case 'dd': + case 'ddd': + case 'dddd': + return parseTokenWord; + case 'a': + case 'A': + return getLangDefinition(config._l)._meridiemParse; + case 'X': + return parseTokenTimestampMs; + case 'Z': + case 'ZZ': + return parseTokenTimezone; + case 'T': + return parseTokenT; + case 'SSSS': + return parseTokenDigits; + case 'MM': + case 'DD': + case 'YY': + case 'GG': + case 'gg': + case 'HH': + case 'hh': + case 'mm': + case 'ss': + case 'ww': + case 'WW': + return strict ? parseTokenTwoDigits : parseTokenOneOrTwoDigits; + case 'M': + case 'D': + case 'd': + case 'H': + case 'h': + case 'm': + case 's': + case 'w': + case 'W': + case 'e': + case 'E': + return parseTokenOneOrTwoDigits; + default : + a = new RegExp(regexpEscape(unescapeFormat(token.replace('\\', '')), "i")); + return a; + } + } + + function timezoneMinutesFromString(string) { + string = string || ""; + var possibleTzMatches = (string.match(parseTokenTimezone) || []), + tzChunk = possibleTzMatches[possibleTzMatches.length - 1] || [], + parts = (tzChunk + '').match(parseTimezoneChunker) || ['-', 0, 0], + minutes = +(parts[1] * 60) + toInt(parts[2]); + + return parts[0] === '+' ? -minutes : minutes; + } + + // function to convert string input to date + function addTimeToArrayFromToken(token, input, config) { + var a, datePartArray = config._a; + + switch (token) { + // MONTH + case 'M' : // fall through to MM + case 'MM' : + if (input != null) { + datePartArray[MONTH] = toInt(input) - 1; + } + break; + case 'MMM' : // fall through to MMMM + case 'MMMM' : + a = getLangDefinition(config._l).monthsParse(input); + // if we didn't find a month name, mark the date as invalid. + if (a != null) { + datePartArray[MONTH] = a; + } else { + config._pf.invalidMonth = input; + } + break; + // DAY OF MONTH + case 'D' : // fall through to DD + case 'DD' : + if (input != null) { + datePartArray[DATE] = toInt(input); + } + break; + // DAY OF YEAR + case 'DDD' : // fall through to DDDD + case 'DDDD' : + if (input != null) { + config._dayOfYear = toInt(input); + } + + break; + // YEAR + case 'YY' : + datePartArray[YEAR] = toInt(input) + (toInt(input) > 68 ? 1900 : 2000); + break; + case 'YYYY' : + case 'YYYYY' : + case 'YYYYYY' : + datePartArray[YEAR] = toInt(input); + break; + // AM / PM + case 'a' : // fall through to A + case 'A' : + config._isPm = getLangDefinition(config._l).isPM(input); + break; + // 24 HOUR + case 'H' : // fall through to hh + case 'HH' : // fall through to hh + case 'h' : // fall through to hh + case 'hh' : + datePartArray[HOUR] = toInt(input); + break; + // MINUTE + case 'm' : // fall through to mm + case 'mm' : + datePartArray[MINUTE] = toInt(input); + break; + // SECOND + case 's' : // fall through to ss + case 'ss' : + datePartArray[SECOND] = toInt(input); + break; + // MILLISECOND + case 'S' : + case 'SS' : + case 'SSS' : + case 'SSSS' : + datePartArray[MILLISECOND] = toInt(('0.' + input) * 1000); + break; + // UNIX TIMESTAMP WITH MS + case 'X': + config._d = new Date(parseFloat(input) * 1000); + break; + // TIMEZONE + case 'Z' : // fall through to ZZ + case 'ZZ' : + config._useUTC = true; + config._tzm = timezoneMinutesFromString(input); + break; + case 'w': + case 'ww': + case 'W': + case 'WW': + case 'd': + case 'dd': + case 'ddd': + case 'dddd': + case 'e': + case 'E': + token = token.substr(0, 1); + /* falls through */ + case 'gg': + case 'gggg': + case 'GG': + case 'GGGG': + case 'GGGGG': + token = token.substr(0, 2); + if (input) { + config._w = config._w || {}; + config._w[token] = input; + } + break; + } + } + + // convert an array to a date. + // the array should mirror the parameters below + // note: all values past the year are optional and will default to the lowest possible value. + // [year, month, day , hour, minute, second, millisecond] + function dateFromConfig(config) { + var i, date, input = [], currentDate, + yearToUse, fixYear, w, temp, lang, weekday, week; + + if (config._d) { + return; + } + + currentDate = currentDateArray(config); + + //compute day of the year from weeks and weekdays + if (config._w && config._a[DATE] == null && config._a[MONTH] == null) { + fixYear = function (val) { + var int_val = parseInt(val, 10); + return val ? + (val.length < 3 ? (int_val > 68 ? 1900 + int_val : 2000 + int_val) : int_val) : + (config._a[YEAR] == null ? moment().weekYear() : config._a[YEAR]); + }; + + w = config._w; + if (w.GG != null || w.W != null || w.E != null) { + temp = dayOfYearFromWeeks(fixYear(w.GG), w.W || 1, w.E, 4, 1); + } + else { + lang = getLangDefinition(config._l); + weekday = w.d != null ? parseWeekday(w.d, lang) : + (w.e != null ? parseInt(w.e, 10) + lang._week.dow : 0); + + week = parseInt(w.w, 10) || 1; + + //if we're parsing 'd', then the low day numbers may be next week + if (w.d != null && weekday < lang._week.dow) { + week++; + } + + temp = dayOfYearFromWeeks(fixYear(w.gg), week, weekday, lang._week.doy, lang._week.dow); + } + + config._a[YEAR] = temp.year; + config._dayOfYear = temp.dayOfYear; + } + + //if the day of the year is set, figure out what it is + if (config._dayOfYear) { + yearToUse = config._a[YEAR] == null ? currentDate[YEAR] : config._a[YEAR]; + + if (config._dayOfYear > daysInYear(yearToUse)) { + config._pf._overflowDayOfYear = true; + } + + date = makeUTCDate(yearToUse, 0, config._dayOfYear); + config._a[MONTH] = date.getUTCMonth(); + config._a[DATE] = date.getUTCDate(); + } + + // Default to current date. + // * if no year, month, day of month are given, default to today + // * if day of month is given, default month and year + // * if month is given, default only year + // * if year is given, don't default anything + for (i = 0; i < 3 && config._a[i] == null; ++i) { + config._a[i] = input[i] = currentDate[i]; + } + + // Zero out whatever was not defaulted, including time + for (; i < 7; i++) { + config._a[i] = input[i] = (config._a[i] == null) ? (i === 2 ? 1 : 0) : config._a[i]; + } + + // add the offsets to the time to be parsed so that we can have a clean array for checking isValid + input[HOUR] += toInt((config._tzm || 0) / 60); + input[MINUTE] += toInt((config._tzm || 0) % 60); + + config._d = (config._useUTC ? makeUTCDate : makeDate).apply(null, input); + } + + function dateFromObject(config) { + var normalizedInput; + + if (config._d) { + return; + } + + normalizedInput = normalizeObjectUnits(config._i); + config._a = [ + normalizedInput.year, + normalizedInput.month, + normalizedInput.day, + normalizedInput.hour, + normalizedInput.minute, + normalizedInput.second, + normalizedInput.millisecond + ]; + + dateFromConfig(config); + } + + function currentDateArray(config) { + var now = new Date(); + if (config._useUTC) { + return [ + now.getUTCFullYear(), + now.getUTCMonth(), + now.getUTCDate() + ]; + } else { + return [now.getFullYear(), now.getMonth(), now.getDate()]; + } + } + + // date from string and format string + function makeDateFromStringAndFormat(config) { + + config._a = []; + config._pf.empty = true; + + // This array is used to make a Date, either with `new Date` or `Date.UTC` + var lang = getLangDefinition(config._l), + string = '' + config._i, + i, parsedInput, tokens, token, skipped, + stringLength = string.length, + totalParsedInputLength = 0; + + tokens = expandFormat(config._f, lang).match(formattingTokens) || []; + + for (i = 0; i < tokens.length; i++) { + token = tokens[i]; + parsedInput = (string.match(getParseRegexForToken(token, config)) || [])[0]; + if (parsedInput) { + skipped = string.substr(0, string.indexOf(parsedInput)); + if (skipped.length > 0) { + config._pf.unusedInput.push(skipped); + } + string = string.slice(string.indexOf(parsedInput) + parsedInput.length); + totalParsedInputLength += parsedInput.length; + } + // don't parse if it's not a known token + if (formatTokenFunctions[token]) { + if (parsedInput) { + config._pf.empty = false; + } + else { + config._pf.unusedTokens.push(token); + } + addTimeToArrayFromToken(token, parsedInput, config); + } + else if (config._strict && !parsedInput) { + config._pf.unusedTokens.push(token); + } + } + + // add remaining unparsed input length to the string + config._pf.charsLeftOver = stringLength - totalParsedInputLength; + if (string.length > 0) { + config._pf.unusedInput.push(string); + } + + // handle am pm + if (config._isPm && config._a[HOUR] < 12) { + config._a[HOUR] += 12; + } + // if is 12 am, change hours to 0 + if (config._isPm === false && config._a[HOUR] === 12) { + config._a[HOUR] = 0; + } + + dateFromConfig(config); + checkOverflow(config); + } + + function unescapeFormat(s) { + return s.replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function (matched, p1, p2, p3, p4) { + return p1 || p2 || p3 || p4; + }); + } + + // Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript + function regexpEscape(s) { + return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); + } + + // date from string and array of format strings + function makeDateFromStringAndArray(config) { + var tempConfig, + bestMoment, + + scoreToBeat, + i, + currentScore; + + if (config._f.length === 0) { + config._pf.invalidFormat = true; + config._d = new Date(NaN); + return; + } + + for (i = 0; i < config._f.length; i++) { + currentScore = 0; + tempConfig = extend({}, config); + tempConfig._pf = defaultParsingFlags(); + tempConfig._f = config._f[i]; + makeDateFromStringAndFormat(tempConfig); + + if (!isValid(tempConfig)) { + continue; + } + + // if there is any input that was not parsed add a penalty for that format + currentScore += tempConfig._pf.charsLeftOver; + + //or tokens + currentScore += tempConfig._pf.unusedTokens.length * 10; + + tempConfig._pf.score = currentScore; + + if (scoreToBeat == null || currentScore < scoreToBeat) { + scoreToBeat = currentScore; + bestMoment = tempConfig; + } + } + + extend(config, bestMoment || tempConfig); + } + + // date from iso format + function makeDateFromString(config) { + var i, l, + string = config._i, + match = isoRegex.exec(string); + + if (match) { + config._pf.iso = true; + for (i = 0, l = isoDates.length; i < l; i++) { + if (isoDates[i][1].exec(string)) { + // match[5] should be "T" or undefined + config._f = isoDates[i][0] + (match[6] || " "); + break; + } + } + for (i = 0, l = isoTimes.length; i < l; i++) { + if (isoTimes[i][1].exec(string)) { + config._f += isoTimes[i][0]; + break; + } + } + if (string.match(parseTokenTimezone)) { + config._f += "Z"; + } + makeDateFromStringAndFormat(config); + } + else { + config._d = new Date(string); + } + } + + function makeDateFromInput(config) { + var input = config._i, + matched = aspNetJsonRegex.exec(input); + + if (input === undefined) { + config._d = new Date(); + } else if (matched) { + config._d = new Date(+matched[1]); + } else if (typeof input === 'string') { + makeDateFromString(config); + } else if (isArray(input)) { + config._a = input.slice(0); + dateFromConfig(config); + } else if (isDate(input)) { + config._d = new Date(+input); + } else if (typeof(input) === 'object') { + dateFromObject(config); + } else { + config._d = new Date(input); + } + } + + function makeDate(y, m, d, h, M, s, ms) { + //can't just apply() to create a date: + //http://stackoverflow.com/questions/181348/instantiating-a-javascript-object-by-calling-prototype-constructor-apply + var date = new Date(y, m, d, h, M, s, ms); + + //the date constructor doesn't accept years < 1970 + if (y < 1970) { + date.setFullYear(y); + } + return date; + } + + function makeUTCDate(y) { + var date = new Date(Date.UTC.apply(null, arguments)); + if (y < 1970) { + date.setUTCFullYear(y); + } + return date; + } + + function parseWeekday(input, language) { + if (typeof input === 'string') { + if (!isNaN(input)) { + input = parseInt(input, 10); + } + else { + input = language.weekdaysParse(input); + if (typeof input !== 'number') { + return null; + } + } + } + return input; + } + + /************************************ + Relative Time + ************************************/ + + + // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize + function substituteTimeAgo(string, number, withoutSuffix, isFuture, lang) { + return lang.relativeTime(number || 1, !!withoutSuffix, string, isFuture); + } + + function relativeTime(milliseconds, withoutSuffix, lang) { + var seconds = round(Math.abs(milliseconds) / 1000), + minutes = round(seconds / 60), + hours = round(minutes / 60), + days = round(hours / 24), + years = round(days / 365), + args = seconds < 45 && ['s', seconds] || + minutes === 1 && ['m'] || + minutes < 45 && ['mm', minutes] || + hours === 1 && ['h'] || + hours < 22 && ['hh', hours] || + days === 1 && ['d'] || + days <= 25 && ['dd', days] || + days <= 45 && ['M'] || + days < 345 && ['MM', round(days / 30)] || + years === 1 && ['y'] || ['yy', years]; + args[2] = withoutSuffix; + args[3] = milliseconds > 0; + args[4] = lang; + return substituteTimeAgo.apply({}, args); + } + + + /************************************ + Week of Year + ************************************/ + + + // firstDayOfWeek 0 = sun, 6 = sat + // the day of the week that starts the week + // (usually sunday or monday) + // firstDayOfWeekOfYear 0 = sun, 6 = sat + // the first week is the week that contains the first + // of this day of the week + // (eg. ISO weeks use thursday (4)) + function weekOfYear(mom, firstDayOfWeek, firstDayOfWeekOfYear) { + var end = firstDayOfWeekOfYear - firstDayOfWeek, + daysToDayOfWeek = firstDayOfWeekOfYear - mom.day(), + adjustedMoment; + + + if (daysToDayOfWeek > end) { + daysToDayOfWeek -= 7; + } + + if (daysToDayOfWeek < end - 7) { + daysToDayOfWeek += 7; + } + + adjustedMoment = moment(mom).add('d', daysToDayOfWeek); + return { + week: Math.ceil(adjustedMoment.dayOfYear() / 7), + year: adjustedMoment.year() + }; + } + + //http://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday + function dayOfYearFromWeeks(year, week, weekday, firstDayOfWeekOfYear, firstDayOfWeek) { + var d = makeUTCDate(year, 0, 1).getUTCDay(), daysToAdd, dayOfYear; + + weekday = weekday != null ? weekday : firstDayOfWeek; + daysToAdd = firstDayOfWeek - d + (d > firstDayOfWeekOfYear ? 7 : 0) - (d < firstDayOfWeek ? 7 : 0); + dayOfYear = 7 * (week - 1) + (weekday - firstDayOfWeek) + daysToAdd + 1; + + return { + year: dayOfYear > 0 ? year : year - 1, + dayOfYear: dayOfYear > 0 ? dayOfYear : daysInYear(year - 1) + dayOfYear + }; + } + + /************************************ + Top Level Functions + ************************************/ + + function makeMoment(config) { + var input = config._i, + format = config._f; + + if (input === null) { + return moment.invalid({nullInput: true}); + } + + if (typeof input === 'string') { + config._i = input = getLangDefinition().preparse(input); + } + + if (moment.isMoment(input)) { + config = cloneMoment(input); + + config._d = new Date(+input._d); + } else if (format) { + if (isArray(format)) { + makeDateFromStringAndArray(config); + } else { + makeDateFromStringAndFormat(config); + } + } else { + makeDateFromInput(config); + } + + return new Moment(config); + } + + moment = function (input, format, lang, strict) { + var c; + + if (typeof(lang) === "boolean") { + strict = lang; + lang = undefined; + } + // object construction must be done this way. + // https://github.com/moment/moment/issues/1423 + c = {}; + c._isAMomentObject = true; + c._i = input; + c._f = format; + c._l = lang; + c._strict = strict; + c._isUTC = false; + c._pf = defaultParsingFlags(); + + return makeMoment(c); + }; + + // creating with utc + moment.utc = function (input, format, lang, strict) { + var c; + + if (typeof(lang) === "boolean") { + strict = lang; + lang = undefined; + } + // object construction must be done this way. + // https://github.com/moment/moment/issues/1423 + c = {}; + c._isAMomentObject = true; + c._useUTC = true; + c._isUTC = true; + c._l = lang; + c._i = input; + c._f = format; + c._strict = strict; + c._pf = defaultParsingFlags(); + + return makeMoment(c).utc(); + }; + + // creating with unix timestamp (in seconds) + moment.unix = function (input) { + return moment(input * 1000); + }; + + // duration + moment.duration = function (input, key) { + var duration = input, + // matching against regexp is expensive, do it on demand + match = null, + sign, + ret, + parseIso; + + if (moment.isDuration(input)) { + duration = { + ms: input._milliseconds, + d: input._days, + M: input._months + }; + } else if (typeof input === 'number') { + duration = {}; + if (key) { + duration[key] = input; + } else { + duration.milliseconds = input; + } + } else if (!!(match = aspNetTimeSpanJsonRegex.exec(input))) { + sign = (match[1] === "-") ? -1 : 1; + duration = { + y: 0, + d: toInt(match[DATE]) * sign, + h: toInt(match[HOUR]) * sign, + m: toInt(match[MINUTE]) * sign, + s: toInt(match[SECOND]) * sign, + ms: toInt(match[MILLISECOND]) * sign + }; + } else if (!!(match = isoDurationRegex.exec(input))) { + sign = (match[1] === "-") ? -1 : 1; + parseIso = function (inp) { + // We'd normally use ~~inp for this, but unfortunately it also + // converts floats to ints. + // inp may be undefined, so careful calling replace on it. + var res = inp && parseFloat(inp.replace(',', '.')); + // apply sign while we're at it + return (isNaN(res) ? 0 : res) * sign; + }; + duration = { + y: parseIso(match[2]), + M: parseIso(match[3]), + d: parseIso(match[4]), + h: parseIso(match[5]), + m: parseIso(match[6]), + s: parseIso(match[7]), + w: parseIso(match[8]) + }; + } + + ret = new Duration(duration); + + if (moment.isDuration(input) && input.hasOwnProperty('_lang')) { + ret._lang = input._lang; + } + + return ret; + }; + + // version number + moment.version = VERSION; + + // default format + moment.defaultFormat = isoFormat; + + // This function will be called whenever a moment is mutated. + // It is intended to keep the offset in sync with the timezone. + moment.updateOffset = function () {}; + + // This function will load languages and then set the global language. If + // no arguments are passed in, it will simply return the current global + // language key. + moment.lang = function (key, values) { + var r; + if (!key) { + return moment.fn._lang._abbr; + } + if (values) { + loadLang(normalizeLanguage(key), values); + } else if (values === null) { + unloadLang(key); + key = 'en'; + } else if (!languages[key]) { + getLangDefinition(key); + } + r = moment.duration.fn._lang = moment.fn._lang = getLangDefinition(key); + return r._abbr; + }; + + // returns language data + moment.langData = function (key) { + if (key && key._lang && key._lang._abbr) { + key = key._lang._abbr; + } + return getLangDefinition(key); + }; + + // compare moment object + moment.isMoment = function (obj) { + return obj instanceof Moment || + (obj != null && obj.hasOwnProperty('_isAMomentObject')); + }; + + // for typechecking Duration objects + moment.isDuration = function (obj) { + return obj instanceof Duration; + }; + + for (i = lists.length - 1; i >= 0; --i) { + makeList(lists[i]); + } + + moment.normalizeUnits = function (units) { + return normalizeUnits(units); + }; + + moment.invalid = function (flags) { + var m = moment.utc(NaN); + if (flags != null) { + extend(m._pf, flags); + } + else { + m._pf.userInvalidated = true; + } + + return m; + }; + + moment.parseZone = function (input) { + return moment(input).parseZone(); + }; + + /************************************ + Moment Prototype + ************************************/ + + + extend(moment.fn = Moment.prototype, { + + clone : function () { + return moment(this); + }, + + valueOf : function () { + return +this._d + ((this._offset || 0) * 60000); + }, + + unix : function () { + return Math.floor(+this / 1000); + }, + + toString : function () { + return this.clone().lang('en').format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ"); + }, + + toDate : function () { + return this._offset ? new Date(+this) : this._d; + }, + + toISOString : function () { + var m = moment(this).utc(); + if (0 < m.year() && m.year() <= 9999) { + return formatMoment(m, 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]'); + } else { + return formatMoment(m, 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]'); + } + }, + + toArray : function () { + var m = this; + return [ + m.year(), + m.month(), + m.date(), + m.hours(), + m.minutes(), + m.seconds(), + m.milliseconds() + ]; + }, + + isValid : function () { + return isValid(this); + }, + + isDSTShifted : function () { + + if (this._a) { + return this.isValid() && compareArrays(this._a, (this._isUTC ? moment.utc(this._a) : moment(this._a)).toArray()) > 0; + } + + return false; + }, + + parsingFlags : function () { + return extend({}, this._pf); + }, + + invalidAt: function () { + return this._pf.overflow; + }, + + utc : function () { + return this.zone(0); + }, + + local : function () { + this.zone(0); + this._isUTC = false; + return this; + }, + + format : function (inputString) { + var output = formatMoment(this, inputString || moment.defaultFormat); + return this.lang().postformat(output); + }, + + add : function (input, val) { + var dur; + // switch args to support add('s', 1) and add(1, 's') + if (typeof input === 'string') { + dur = moment.duration(+val, input); + } else { + dur = moment.duration(input, val); + } + addOrSubtractDurationFromMoment(this, dur, 1); + return this; + }, + + subtract : function (input, val) { + var dur; + // switch args to support subtract('s', 1) and subtract(1, 's') + if (typeof input === 'string') { + dur = moment.duration(+val, input); + } else { + dur = moment.duration(input, val); + } + addOrSubtractDurationFromMoment(this, dur, -1); + return this; + }, + + diff : function (input, units, asFloat) { + var that = makeAs(input, this), + zoneDiff = (this.zone() - that.zone()) * 6e4, + diff, output; + + units = normalizeUnits(units); + + if (units === 'year' || units === 'month') { + // average number of days in the months in the given dates + diff = (this.daysInMonth() + that.daysInMonth()) * 432e5; // 24 * 60 * 60 * 1000 / 2 + // difference in months + output = ((this.year() - that.year()) * 12) + (this.month() - that.month()); + // adjust by taking difference in days, average number of days + // and dst in the given months. + output += ((this - moment(this).startOf('month')) - + (that - moment(that).startOf('month'))) / diff; + // same as above but with zones, to negate all dst + output -= ((this.zone() - moment(this).startOf('month').zone()) - + (that.zone() - moment(that).startOf('month').zone())) * 6e4 / diff; + if (units === 'year') { + output = output / 12; + } + } else { + diff = (this - that); + output = units === 'second' ? diff / 1e3 : // 1000 + units === 'minute' ? diff / 6e4 : // 1000 * 60 + units === 'hour' ? diff / 36e5 : // 1000 * 60 * 60 + units === 'day' ? (diff - zoneDiff) / 864e5 : // 1000 * 60 * 60 * 24, negate dst + units === 'week' ? (diff - zoneDiff) / 6048e5 : // 1000 * 60 * 60 * 24 * 7, negate dst + diff; + } + return asFloat ? output : absRound(output); + }, + + from : function (time, withoutSuffix) { + return moment.duration(this.diff(time)).lang(this.lang()._abbr).humanize(!withoutSuffix); + }, + + fromNow : function (withoutSuffix) { + return this.from(moment(), withoutSuffix); + }, + + calendar : function () { + // We want to compare the start of today, vs this. + // Getting start-of-today depends on whether we're zone'd or not. + var sod = makeAs(moment(), this).startOf('day'), + diff = this.diff(sod, 'days', true), + format = diff < -6 ? 'sameElse' : + diff < -1 ? 'lastWeek' : + diff < 0 ? 'lastDay' : + diff < 1 ? 'sameDay' : + diff < 2 ? 'nextDay' : + diff < 7 ? 'nextWeek' : 'sameElse'; + return this.format(this.lang().calendar(format, this)); + }, + + isLeapYear : function () { + return isLeapYear(this.year()); + }, + + isDST : function () { + return (this.zone() < this.clone().month(0).zone() || + this.zone() < this.clone().month(5).zone()); + }, + + day : function (input) { + var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay(); + if (input != null) { + input = parseWeekday(input, this.lang()); + return this.add({ d : input - day }); + } else { + return day; + } + }, + + month : function (input) { + var utc = this._isUTC ? 'UTC' : '', + dayOfMonth; + + if (input != null) { + if (typeof input === 'string') { + input = this.lang().monthsParse(input); + if (typeof input !== 'number') { + return this; + } + } + + dayOfMonth = this.date(); + this.date(1); + this._d['set' + utc + 'Month'](input); + this.date(Math.min(dayOfMonth, this.daysInMonth())); + + moment.updateOffset(this); + return this; + } else { + return this._d['get' + utc + 'Month'](); + } + }, + + startOf: function (units) { + units = normalizeUnits(units); + // the following switch intentionally omits break keywords + // to utilize falling through the cases. + switch (units) { + case 'year': + this.month(0); + /* falls through */ + case 'month': + this.date(1); + /* falls through */ + case 'week': + case 'isoWeek': + case 'day': + this.hours(0); + /* falls through */ + case 'hour': + this.minutes(0); + /* falls through */ + case 'minute': + this.seconds(0); + /* falls through */ + case 'second': + this.milliseconds(0); + /* falls through */ + } + + // weeks are a special case + if (units === 'week') { + this.weekday(0); + } else if (units === 'isoWeek') { + this.isoWeekday(1); + } + + return this; + }, + + endOf: function (units) { + units = normalizeUnits(units); + return this.startOf(units).add((units === 'isoWeek' ? 'week' : units), 1).subtract('ms', 1); + }, + + isAfter: function (input, units) { + units = typeof units !== 'undefined' ? units : 'millisecond'; + return +this.clone().startOf(units) > +moment(input).startOf(units); + }, + + isBefore: function (input, units) { + units = typeof units !== 'undefined' ? units : 'millisecond'; + return +this.clone().startOf(units) < +moment(input).startOf(units); + }, + + isSame: function (input, units) { + units = units || 'ms'; + return +this.clone().startOf(units) === +makeAs(input, this).startOf(units); + }, + + min: function (other) { + other = moment.apply(null, arguments); + return other < this ? this : other; + }, + + max: function (other) { + other = moment.apply(null, arguments); + return other > this ? this : other; + }, + + zone : function (input) { + var offset = this._offset || 0; + if (input != null) { + if (typeof input === "string") { + input = timezoneMinutesFromString(input); + } + if (Math.abs(input) < 16) { + input = input * 60; + } + this._offset = input; + this._isUTC = true; + if (offset !== input) { + addOrSubtractDurationFromMoment(this, moment.duration(offset - input, 'm'), 1, true); + } + } else { + return this._isUTC ? offset : this._d.getTimezoneOffset(); + } + return this; + }, + + zoneAbbr : function () { + return this._isUTC ? "UTC" : ""; + }, + + zoneName : function () { + return this._isUTC ? "Coordinated Universal Time" : ""; + }, + + parseZone : function () { + if (this._tzm) { + this.zone(this._tzm); + } else if (typeof this._i === 'string') { + this.zone(this._i); + } + return this; + }, + + hasAlignedHourOffset : function (input) { + if (!input) { + input = 0; + } + else { + input = moment(input).zone(); + } + + return (this.zone() - input) % 60 === 0; + }, + + daysInMonth : function () { + return daysInMonth(this.year(), this.month()); + }, + + dayOfYear : function (input) { + var dayOfYear = round((moment(this).startOf('day') - moment(this).startOf('year')) / 864e5) + 1; + return input == null ? dayOfYear : this.add("d", (input - dayOfYear)); + }, + + quarter : function () { + return Math.ceil((this.month() + 1.0) / 3.0); + }, + + weekYear : function (input) { + var year = weekOfYear(this, this.lang()._week.dow, this.lang()._week.doy).year; + return input == null ? year : this.add("y", (input - year)); + }, + + isoWeekYear : function (input) { + var year = weekOfYear(this, 1, 4).year; + return input == null ? year : this.add("y", (input - year)); + }, + + week : function (input) { + var week = this.lang().week(this); + return input == null ? week : this.add("d", (input - week) * 7); + }, + + isoWeek : function (input) { + var week = weekOfYear(this, 1, 4).week; + return input == null ? week : this.add("d", (input - week) * 7); + }, + + weekday : function (input) { + var weekday = (this.day() + 7 - this.lang()._week.dow) % 7; + return input == null ? weekday : this.add("d", input - weekday); + }, + + isoWeekday : function (input) { + // behaves the same as moment#day except + // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6) + // as a setter, sunday should belong to the previous week. + return input == null ? this.day() || 7 : this.day(this.day() % 7 ? input : input - 7); + }, + + get : function (units) { + units = normalizeUnits(units); + return this[units](); + }, + + set : function (units, value) { + units = normalizeUnits(units); + if (typeof this[units] === 'function') { + this[units](value); + } + return this; + }, + + // If passed a language key, it will set the language for this + // instance. Otherwise, it will return the language configuration + // variables for this instance. + lang : function (key) { + if (key === undefined) { + return this._lang; + } else { + this._lang = getLangDefinition(key); + return this; + } + } + }); + + // helper for adding shortcuts + function makeGetterAndSetter(name, key) { + moment.fn[name] = moment.fn[name + 's'] = function (input) { + var utc = this._isUTC ? 'UTC' : ''; + if (input != null) { + this._d['set' + utc + key](input); + moment.updateOffset(this); + return this; + } else { + return this._d['get' + utc + key](); + } + }; + } + + // loop through and add shortcuts (Month, Date, Hours, Minutes, Seconds, Milliseconds) + for (i = 0; i < proxyGettersAndSetters.length; i ++) { + makeGetterAndSetter(proxyGettersAndSetters[i].toLowerCase().replace(/s$/, ''), proxyGettersAndSetters[i]); + } + + // add shortcut for year (uses different syntax than the getter/setter 'year' == 'FullYear') + makeGetterAndSetter('year', 'FullYear'); + + // add plural methods + moment.fn.days = moment.fn.day; + moment.fn.months = moment.fn.month; + moment.fn.weeks = moment.fn.week; + moment.fn.isoWeeks = moment.fn.isoWeek; + + // add aliased format methods + moment.fn.toJSON = moment.fn.toISOString; + + /************************************ + Duration Prototype + ************************************/ + + + extend(moment.duration.fn = Duration.prototype, { + + _bubble : function () { + var milliseconds = this._milliseconds, + days = this._days, + months = this._months, + data = this._data, + seconds, minutes, hours, years; + + // The following code bubbles up values, see the tests for + // examples of what that means. + data.milliseconds = milliseconds % 1000; + + seconds = absRound(milliseconds / 1000); + data.seconds = seconds % 60; + + minutes = absRound(seconds / 60); + data.minutes = minutes % 60; + + hours = absRound(minutes / 60); + data.hours = hours % 24; + + days += absRound(hours / 24); + data.days = days % 30; + + months += absRound(days / 30); + data.months = months % 12; + + years = absRound(months / 12); + data.years = years; + }, + + weeks : function () { + return absRound(this.days() / 7); + }, + + valueOf : function () { + return this._milliseconds + + this._days * 864e5 + + (this._months % 12) * 2592e6 + + toInt(this._months / 12) * 31536e6; + }, + + humanize : function (withSuffix) { + var difference = +this, + output = relativeTime(difference, !withSuffix, this.lang()); + + if (withSuffix) { + output = this.lang().pastFuture(difference, output); + } + + return this.lang().postformat(output); + }, + + add : function (input, val) { + // supports only 2.0-style add(1, 's') or add(moment) + var dur = moment.duration(input, val); + + this._milliseconds += dur._milliseconds; + this._days += dur._days; + this._months += dur._months; + + this._bubble(); + + return this; + }, + + subtract : function (input, val) { + var dur = moment.duration(input, val); + + this._milliseconds -= dur._milliseconds; + this._days -= dur._days; + this._months -= dur._months; + + this._bubble(); + + return this; + }, + + get : function (units) { + units = normalizeUnits(units); + return this[units.toLowerCase() + 's'](); + }, + + as : function (units) { + units = normalizeUnits(units); + return this['as' + units.charAt(0).toUpperCase() + units.slice(1) + 's'](); + }, + + lang : moment.fn.lang, + + toIsoString : function () { + // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js + var years = Math.abs(this.years()), + months = Math.abs(this.months()), + days = Math.abs(this.days()), + hours = Math.abs(this.hours()), + minutes = Math.abs(this.minutes()), + seconds = Math.abs(this.seconds() + this.milliseconds() / 1000); + + if (!this.asSeconds()) { + // this is the same as C#'s (Noda) and python (isodate)... + // but not other JS (goog.date) + return 'P0D'; + } + + return (this.asSeconds() < 0 ? '-' : '') + + 'P' + + (years ? years + 'Y' : '') + + (months ? months + 'M' : '') + + (days ? days + 'D' : '') + + ((hours || minutes || seconds) ? 'T' : '') + + (hours ? hours + 'H' : '') + + (minutes ? minutes + 'M' : '') + + (seconds ? seconds + 'S' : ''); + } + }); + + function makeDurationGetter(name) { + moment.duration.fn[name] = function () { + return this._data[name]; + }; + } + + function makeDurationAsGetter(name, factor) { + moment.duration.fn['as' + name] = function () { + return +this / factor; + }; + } + + for (i in unitMillisecondFactors) { + if (unitMillisecondFactors.hasOwnProperty(i)) { + makeDurationAsGetter(i, unitMillisecondFactors[i]); + makeDurationGetter(i.toLowerCase()); + } + } + + makeDurationAsGetter('Weeks', 6048e5); + moment.duration.fn.asMonths = function () { + return (+this - this.years() * 31536e6) / 2592e6 + this.years() * 12; + }; + + + /************************************ + Default Lang + ************************************/ + + + // Set default language, other languages will inherit from English. + moment.lang('en', { + ordinal : function (number) { + var b = number % 10, + output = (toInt(number % 100 / 10) === 1) ? 'th' : + (b === 1) ? 'st' : + (b === 2) ? 'nd' : + (b === 3) ? 'rd' : 'th'; + return number + output; + } + }); + + /* EMBED_LANGUAGES */ + + /************************************ + Exposing Moment + ************************************/ + + function makeGlobal(deprecate) { + var warned = false, local_moment = moment; + /*global ender:false */ + if (typeof ender !== 'undefined') { + return; + } + // here, `this` means `window` in the browser, or `global` on the server + // add `moment` as a global object via a string identifier, + // for Closure Compiler "advanced" mode + if (deprecate) { + global.moment = function () { + if (!warned && console && console.warn) { + warned = true; + console.warn( + "Accessing Moment through the global scope is " + + "deprecated, and will be removed in an upcoming " + + "release."); + } + return local_moment.apply(null, arguments); + }; + extend(global.moment, local_moment); + } else { + global['moment'] = moment; + } + } + + // CommonJS module is defined + if (hasModule) { + module.exports = moment; + makeGlobal(true); + } else if (typeof define === "function" && define.amd) { + define("moment", function (require, exports, module) { + if (module.config && module.config() && module.config().noGlobal !== true) { + // If user provided noGlobal, he is aware of global + makeGlobal(module.config().noGlobal === undefined); + } + + return moment; + }); + } else { + makeGlobal(); + } +}).call(this); + +},{}],5:[function(require,module,exports){ +/** + * Copyright 2012 Craig Campbell + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Mousetrap is a simple keyboard shortcut library for Javascript with + * no external dependencies + * + * @version 1.1.2 + * @url craig.is/killing/mice + */ + + /** + * mapping of special keycodes to their corresponding keys + * + * everything in this dictionary cannot use keypress events + * so it has to be here to map to the correct keycodes for + * keyup/keydown events + * + * @type {Object} + */ + var _MAP = { + 8: 'backspace', + 9: 'tab', + 13: 'enter', + 16: 'shift', + 17: 'ctrl', + 18: 'alt', + 20: 'capslock', + 27: 'esc', + 32: 'space', + 33: 'pageup', + 34: 'pagedown', + 35: 'end', + 36: 'home', + 37: 'left', + 38: 'up', + 39: 'right', + 40: 'down', + 45: 'ins', + 46: 'del', + 91: 'meta', + 93: 'meta', + 224: 'meta' + }, + + /** + * mapping for special characters so they can support + * + * this dictionary is only used incase you want to bind a + * keyup or keydown event to one of these keys + * + * @type {Object} + */ + _KEYCODE_MAP = { + 106: '*', + 107: '+', + 109: '-', + 110: '.', + 111 : '/', + 186: ';', + 187: '=', + 188: ',', + 189: '-', + 190: '.', + 191: '/', + 192: '`', + 219: '[', + 220: '\\', + 221: ']', + 222: '\'' + }, + + /** + * this is a mapping of keys that require shift on a US keypad + * back to the non shift equivelents + * + * this is so you can use keyup events with these keys + * + * note that this will only work reliably on US keyboards + * + * @type {Object} + */ + _SHIFT_MAP = { + '~': '`', + '!': '1', + '@': '2', + '#': '3', + '$': '4', + '%': '5', + '^': '6', + '&': '7', + '*': '8', + '(': '9', + ')': '0', + '_': '-', + '+': '=', + ':': ';', + '\"': '\'', + '<': ',', + '>': '.', + '?': '/', + '|': '\\' + }, + + /** + * this is a list of special strings you can use to map + * to modifier keys when you specify your keyboard shortcuts + * + * @type {Object} + */ + _SPECIAL_ALIASES = { + 'option': 'alt', + 'command': 'meta', + 'return': 'enter', + 'escape': 'esc' + }, + + /** + * variable to store the flipped version of _MAP from above + * needed to check if we should use keypress or not when no action + * is specified + * + * @type {Object|undefined} + */ + _REVERSE_MAP, + + /** + * a list of all the callbacks setup via Mousetrap.bind() + * + * @type {Object} + */ + _callbacks = {}, + + /** + * direct map of string combinations to callbacks used for trigger() + * + * @type {Object} + */ + _direct_map = {}, + + /** + * keeps track of what level each sequence is at since multiple + * sequences can start out with the same sequence + * + * @type {Object} + */ + _sequence_levels = {}, + + /** + * variable to store the setTimeout call + * + * @type {null|number} + */ + _reset_timer, + + /** + * temporary state where we will ignore the next keyup + * + * @type {boolean|string} + */ + _ignore_next_keyup = false, + + /** + * are we currently inside of a sequence? + * type of action ("keyup" or "keydown" or "keypress") or false + * + * @type {boolean|string} + */ + _inside_sequence = false; + + /** + * loop through the f keys, f1 to f19 and add them to the map + * programatically + */ + for (var i = 1; i < 20; ++i) { + _MAP[111 + i] = 'f' + i; + } + + /** + * loop through to map numbers on the numeric keypad + */ + for (i = 0; i <= 9; ++i) { + _MAP[i + 96] = i; + } + + /** + * cross browser add event method + * + * @param {Element|HTMLDocument} object + * @param {string} type + * @param {Function} callback + * @returns void + */ + function _addEvent(object, type, callback) { + if (object.addEventListener) { + return object.addEventListener(type, callback, false); + } + + object.attachEvent('on' + type, callback); + } + + /** + * takes the event and returns the key character + * + * @param {Event} e + * @return {string} + */ + function _characterFromEvent(e) { + + // for keypress events we should return the character as is + if (e.type == 'keypress') { + return String.fromCharCode(e.which); + } + + // for non keypress events the special maps are needed + if (_MAP[e.which]) { + return _MAP[e.which]; + } + + if (_KEYCODE_MAP[e.which]) { + return _KEYCODE_MAP[e.which]; + } + + // if it is not in the special map + return String.fromCharCode(e.which).toLowerCase(); + } + + /** + * should we stop this event before firing off callbacks + * + * @param {Event} e + * @return {boolean} + */ + function _stop(e) { + var element = e.target || e.srcElement, + tag_name = element.tagName; + + // if the element has the class "mousetrap" then no need to stop + if ((' ' + element.className + ' ').indexOf(' mousetrap ') > -1) { + return false; + } + + // stop for input, select, and textarea + return tag_name == 'INPUT' || tag_name == 'SELECT' || tag_name == 'TEXTAREA' || (element.contentEditable && element.contentEditable == 'true'); + } + + /** + * checks if two arrays are equal + * + * @param {Array} modifiers1 + * @param {Array} modifiers2 + * @returns {boolean} + */ + function _modifiersMatch(modifiers1, modifiers2) { + return modifiers1.sort().join(',') === modifiers2.sort().join(','); + } + + /** + * resets all sequence counters except for the ones passed in + * + * @param {Object} do_not_reset + * @returns void + */ + function _resetSequences(do_not_reset) { + do_not_reset = do_not_reset || {}; + + var active_sequences = false, + key; + + for (key in _sequence_levels) { + if (do_not_reset[key]) { + active_sequences = true; + continue; + } + _sequence_levels[key] = 0; + } + + if (!active_sequences) { + _inside_sequence = false; + } + } + + /** + * finds all callbacks that match based on the keycode, modifiers, + * and action + * + * @param {string} character + * @param {Array} modifiers + * @param {string} action + * @param {boolean=} remove - should we remove any matches + * @param {string=} combination + * @returns {Array} + */ + function _getMatches(character, modifiers, action, remove, combination) { + var i, + callback, + matches = []; + + // if there are no events related to this keycode + if (!_callbacks[character]) { + return []; + } + + // if a modifier key is coming up on its own we should allow it + if (action == 'keyup' && _isModifier(character)) { + modifiers = [character]; + } + + // loop through all callbacks for the key that was pressed + // and see if any of them match + for (i = 0; i < _callbacks[character].length; ++i) { + callback = _callbacks[character][i]; + + // if this is a sequence but it is not at the right level + // then move onto the next match + if (callback.seq && _sequence_levels[callback.seq] != callback.level) { + continue; + } + + // if the action we are looking for doesn't match the action we got + // then we should keep going + if (action != callback.action) { + continue; + } + + // if this is a keypress event that means that we need to only + // look at the character, otherwise check the modifiers as + // well + if (action == 'keypress' || _modifiersMatch(modifiers, callback.modifiers)) { + + // remove is used so if you change your mind and call bind a + // second time with a new function the first one is overwritten + if (remove && callback.combo == combination) { + _callbacks[character].splice(i, 1); + } + + matches.push(callback); + } + } + + return matches; + } + + /** + * takes a key event and figures out what the modifiers are + * + * @param {Event} e + * @returns {Array} + */ + function _eventModifiers(e) { + var modifiers = []; + + if (e.shiftKey) { + modifiers.push('shift'); + } + + if (e.altKey) { + modifiers.push('alt'); + } + + if (e.ctrlKey) { + modifiers.push('ctrl'); + } + + if (e.metaKey) { + modifiers.push('meta'); + } + + return modifiers; + } + + /** + * actually calls the callback function + * + * if your callback function returns false this will use the jquery + * convention - prevent default and stop propogation on the event + * + * @param {Function} callback + * @param {Event} e + * @returns void + */ + function _fireCallback(callback, e) { + if (callback(e) === false) { + if (e.preventDefault) { + e.preventDefault(); + } + + if (e.stopPropagation) { + e.stopPropagation(); + } + + e.returnValue = false; + e.cancelBubble = true; + } + } + + /** + * handles a character key event + * + * @param {string} character + * @param {Event} e + * @returns void + */ + function _handleCharacter(character, e) { + + // if this event should not happen stop here + if (_stop(e)) { + return; + } + + var callbacks = _getMatches(character, _eventModifiers(e), e.type), + i, + do_not_reset = {}, + processed_sequence_callback = false; + + // loop through matching callbacks for this key event + for (i = 0; i < callbacks.length; ++i) { + + // fire for all sequence callbacks + // this is because if for example you have multiple sequences + // bound such as "g i" and "g t" they both need to fire the + // callback for matching g cause otherwise you can only ever + // match the first one + if (callbacks[i].seq) { + processed_sequence_callback = true; + + // keep a list of which sequences were matches for later + do_not_reset[callbacks[i].seq] = 1; + _fireCallback(callbacks[i].callback, e); + continue; + } + + // if there were no sequence matches but we are still here + // that means this is a regular match so we should fire that + if (!processed_sequence_callback && !_inside_sequence) { + _fireCallback(callbacks[i].callback, e); + } + } + + // if you are inside of a sequence and the key you are pressing + // is not a modifier key then we should reset all sequences + // that were not matched by this key event + if (e.type == _inside_sequence && !_isModifier(character)) { + _resetSequences(do_not_reset); + } + } + + /** + * handles a keydown event + * + * @param {Event} e + * @returns void + */ + function _handleKey(e) { + + // normalize e.which for key events + // @see http://stackoverflow.com/questions/4285627/javascript-keycode-vs-charcode-utter-confusion + e.which = typeof e.which == "number" ? e.which : e.keyCode; + + var character = _characterFromEvent(e); + + // no character found then stop + if (!character) { + return; + } + + if (e.type == 'keyup' && _ignore_next_keyup == character) { + _ignore_next_keyup = false; + return; + } + + _handleCharacter(character, e); + } + + /** + * determines if the keycode specified is a modifier key or not + * + * @param {string} key + * @returns {boolean} + */ + function _isModifier(key) { + return key == 'shift' || key == 'ctrl' || key == 'alt' || key == 'meta'; + } + + /** + * called to set a 1 second timeout on the specified sequence + * + * this is so after each key press in the sequence you have 1 second + * to press the next key before you have to start over + * + * @returns void + */ + function _resetSequenceTimer() { + clearTimeout(_reset_timer); + _reset_timer = setTimeout(_resetSequences, 1000); + } + + /** + * reverses the map lookup so that we can look for specific keys + * to see what can and can't use keypress + * + * @return {Object} + */ + function _getReverseMap() { + if (!_REVERSE_MAP) { + _REVERSE_MAP = {}; + for (var key in _MAP) { + + // pull out the numeric keypad from here cause keypress should + // be able to detect the keys from the character + if (key > 95 && key < 112) { + continue; + } + + if (_MAP.hasOwnProperty(key)) { + _REVERSE_MAP[_MAP[key]] = key; + } + } + } + return _REVERSE_MAP; + } + + /** + * picks the best action based on the key combination + * + * @param {string} key - character for key + * @param {Array} modifiers + * @param {string=} action passed in + */ + function _pickBestAction(key, modifiers, action) { + + // if no action was picked in we should try to pick the one + // that we think would work best for this key + if (!action) { + action = _getReverseMap()[key] ? 'keydown' : 'keypress'; + } + + // modifier keys don't work as expected with keypress, + // switch to keydown + if (action == 'keypress' && modifiers.length) { + action = 'keydown'; + } + + return action; + } + + /** + * binds a key sequence to an event + * + * @param {string} combo - combo specified in bind call + * @param {Array} keys + * @param {Function} callback + * @param {string=} action + * @returns void + */ + function _bindSequence(combo, keys, callback, action) { + + // start off by adding a sequence level record for this combination + // and setting the level to 0 + _sequence_levels[combo] = 0; + + // if there is no action pick the best one for the first key + // in the sequence + if (!action) { + action = _pickBestAction(keys[0], []); + } + + /** + * callback to increase the sequence level for this sequence and reset + * all other sequences that were active + * + * @param {Event} e + * @returns void + */ + var _increaseSequence = function(e) { + _inside_sequence = action; + ++_sequence_levels[combo]; + _resetSequenceTimer(); + }, + + /** + * wraps the specified callback inside of another function in order + * to reset all sequence counters as soon as this sequence is done + * + * @param {Event} e + * @returns void + */ + _callbackAndReset = function(e) { + _fireCallback(callback, e); + + // we should ignore the next key up if the action is key down + // or keypress. this is so if you finish a sequence and + // release the key the final key will not trigger a keyup + if (action !== 'keyup') { + _ignore_next_keyup = _characterFromEvent(e); + } + + // weird race condition if a sequence ends with the key + // another sequence begins with + setTimeout(_resetSequences, 10); + }, + i; + + // loop through keys one at a time and bind the appropriate callback + // function. for any key leading up to the final one it should + // increase the sequence. after the final, it should reset all sequences + for (i = 0; i < keys.length; ++i) { + _bindSingle(keys[i], i < keys.length - 1 ? _increaseSequence : _callbackAndReset, action, combo, i); + } + } + + /** + * binds a single keyboard combination + * + * @param {string} combination + * @param {Function} callback + * @param {string=} action + * @param {string=} sequence_name - name of sequence if part of sequence + * @param {number=} level - what part of the sequence the command is + * @returns void + */ + function _bindSingle(combination, callback, action, sequence_name, level) { + + // make sure multiple spaces in a row become a single space + combination = combination.replace(/\s+/g, ' '); + + var sequence = combination.split(' '), + i, + key, + keys, + modifiers = []; + + // if this pattern is a sequence of keys then run through this method + // to reprocess each pattern one key at a time + if (sequence.length > 1) { + return _bindSequence(combination, sequence, callback, action); + } + + // take the keys from this pattern and figure out what the actual + // pattern is all about + keys = combination === '+' ? ['+'] : combination.split('+'); + + for (i = 0; i < keys.length; ++i) { + key = keys[i]; + + // normalize key names + if (_SPECIAL_ALIASES[key]) { + key = _SPECIAL_ALIASES[key]; + } + + // if this is not a keypress event then we should + // be smart about using shift keys + // this will only work for US keyboards however + if (action && action != 'keypress' && _SHIFT_MAP[key]) { + key = _SHIFT_MAP[key]; + modifiers.push('shift'); + } + + // if this key is a modifier then add it to the list of modifiers + if (_isModifier(key)) { + modifiers.push(key); + } + } + + // depending on what the key combination is + // we will try to pick the best event for it + action = _pickBestAction(key, modifiers, action); + + // make sure to initialize array if this is the first time + // a callback is added for this key + if (!_callbacks[key]) { + _callbacks[key] = []; + } + + // remove an existing match if there is one + _getMatches(key, modifiers, action, !sequence_name, combination); + + // add this call back to the array + // if it is a sequence put it at the beginning + // if not put it at the end + // + // this is important because the way these are processed expects + // the sequence ones to come first + _callbacks[key][sequence_name ? 'unshift' : 'push']({ + callback: callback, + modifiers: modifiers, + action: action, + seq: sequence_name, + level: level, + combo: combination + }); + } + + /** + * binds multiple combinations to the same callback + * + * @param {Array} combinations + * @param {Function} callback + * @param {string|undefined} action + * @returns void + */ + function _bindMultiple(combinations, callback, action) { + for (var i = 0; i < combinations.length; ++i) { + _bindSingle(combinations[i], callback, action); + } + } + + // start! + _addEvent(document, 'keypress', _handleKey); + _addEvent(document, 'keydown', _handleKey); + _addEvent(document, 'keyup', _handleKey); + + var mousetrap = { + + /** + * binds an event to mousetrap + * + * can be a single key, a combination of keys separated with +, + * a comma separated list of keys, an array of keys, or + * a sequence of keys separated by spaces + * + * be sure to list the modifier keys first to make sure that the + * correct key ends up getting bound (the last key in the pattern) + * + * @param {string|Array} keys + * @param {Function} callback + * @param {string=} action - 'keypress', 'keydown', or 'keyup' + * @returns void + */ + bind: function(keys, callback, action) { + _bindMultiple(keys instanceof Array ? keys : [keys], callback, action); + _direct_map[keys + ':' + action] = callback; + return this; + }, + + /** + * unbinds an event to mousetrap + * + * the unbinding sets the callback function of the specified key combo + * to an empty function and deletes the corresponding key in the + * _direct_map dict. + * + * the keycombo+action has to be exactly the same as + * it was defined in the bind method + * + * TODO: actually remove this from the _callbacks dictionary instead + * of binding an empty function + * + * @param {string|Array} keys + * @param {string} action + * @returns void + */ + unbind: function(keys, action) { + if (_direct_map[keys + ':' + action]) { + delete _direct_map[keys + ':' + action]; + this.bind(keys, function() {}, action); + } + return this; + }, + + /** + * triggers an event that has already been bound + * + * @param {string} keys + * @param {string=} action + * @returns void + */ + trigger: function(keys, action) { + _direct_map[keys + ':' + action](); + return this; + }, + + /** + * resets the library back to its initial state. this is useful + * if you want to clear out the current keyboard shortcuts and bind + * new ones - for example if you switch to another page + * + * @returns void + */ + reset: function() { + _callbacks = {}; + _direct_map = {}; + return this; + } + }; + +module.exports = mousetrap; + + +},{}]},{},[1]) +(1) +}); \ No newline at end of file diff --git a/web/dist/vis.min.css b/web/dist/vis.min.css new file mode 100644 index 0000000..8e8f959 --- /dev/null +++ b/web/dist/vis.min.css @@ -0,0 +1 @@ +.vis.timeline.rootpanel{position:relative;overflow:hidden;border:1px solid #bfbfbf;-moz-box-sizing:border-box;box-sizing:border-box}.vis.timeline .vpanel{position:absolute;overflow:hidden}.vis.timeline .groupset{position:absolute;padding:0;margin:0}.vis.timeline .labels{position:absolute;top:0;left:0;width:100%;height:100%;padding:0;margin:0;border-right:1px solid #bfbfbf;box-sizing:border-box;-moz-box-sizing:border-box}.vis.timeline .labels .label-set{position:absolute;top:0;left:0;width:100%;height:100%;overflow:hidden;border-top:none;border-bottom:1px solid #bfbfbf}.vis.timeline .labels .label-set .vlabel{position:absolute;left:0;top:0;width:100%;color:#4d4d4d}.vis.timeline.top .groupset .itemset-axis,.vis.timeline.top .labels .label-set .vlabel{border-top:1px solid #bfbfbf;border-bottom:none}.vis.timeline.bottom .groupset .itemset-axis,.vis.timeline.bottom .labels .label-set .vlabel{border-top:none;border-bottom:1px solid #bfbfbf}.vis.timeline .labels .label-set .vlabel .inner{display:inline-block;padding:5px}.vis.timeline .itemset{position:absolute;padding:0;margin:0;overflow:hidden}.vis.timeline .itemset-axis{position:absolute}.vis.timeline .item{position:absolute;color:#1A1A1A;border-color:#97B0F8;background-color:#D5DDF6;display:inline-block;padding:5px}.vis.timeline .item.selected{border-color:#FFC200;background-color:#FFF785;z-index:999}.vis.timeline.editable .item.selected{cursor:move}.vis.timeline .item.point.selected{background-color:#FFF785;z-index:999}.vis.timeline .item.point.selected .dot{border-color:#FFC200}.vis.timeline .item.cluster{background:#97B0F8 url(img/cluster_bg.png);color:#fff}.vis.timeline .item.cluster.point{border-color:#D5DDF6}.vis.timeline .item.box{text-align:center;border-style:solid;border-width:1px;border-radius:5px;-moz-border-radius:5px}.vis.timeline .item.point{background:0 0}.vis.timeline .dot,.vis.timeline .item.dot{padding:0;border:5px solid #97B0F8;position:absolute;border-radius:5px;-moz-border-radius:5px}.vis.timeline .item.range,.vis.timeline .item.rangeoverflow{border-style:solid;border-width:1px;border-radius:2px;-moz-border-radius:2px;box-sizing:border-box}.vis.timeline .item.range .content,.vis.timeline .item.rangeoverflow .content{position:relative;display:inline-block}.vis.timeline .item.range .content{overflow:hidden;max-width:100%}.vis.timeline .item.line{padding:0;position:absolute;width:0;border-left-width:1px;border-left-style:solid}.vis.timeline .item .content{white-space:nowrap;overflow:hidden}.vis.timeline .item .delete{background:url(img/timeline/delete.png) no-repeat top center;position:absolute;width:24px;height:24px;top:0;right:-24px;cursor:pointer}.vis.timeline .item.range .drag-left,.vis.timeline .item.rangeoverflow .drag-left{position:absolute;width:24px;height:100%;top:0;left:-4px;cursor:w-resize;z-index:10000}.vis.timeline .item.range .drag-right,.vis.timeline .item.rangeoverflow .drag-right{position:absolute;width:24px;height:100%;top:0;right:-4px;cursor:e-resize;z-index:10001}.vis.timeline .axis{position:relative}.vis.timeline .axis .text{position:absolute;color:#4d4d4d;padding:3px;white-space:nowrap}.vis.timeline .axis .text.measure{position:absolute;padding-left:0;padding-right:0;margin-left:0;margin-right:0;visibility:hidden}.vis.timeline .axis .grid.vertical{position:absolute;width:0;border-right:1px solid}.vis.timeline .axis .grid.horizontal{position:absolute;left:0;width:100%;height:0;border-bottom:1px solid}.vis.timeline .axis .grid.minor{border-color:#e5e5e5}.vis.timeline .axis .grid.major{border-color:#bfbfbf}.vis.timeline .currenttime{background-color:#FF7F6E;width:2px;z-index:9}.vis.timeline .customtime{background-color:#6E94FF;width:2px;cursor:move;z-index:9}div.graph-manipulationDiv{border-width:0;border-bottom:1px;border-style:solid;border-color:#d6d9d8;background:#fff;background:-moz-linear-gradient(top,#fff 0,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#fff),color-stop(48%,#fcfcfc),color-stop(50%,#fafafa),color-stop(100%,#fcfcfc));background:-webkit-linear-gradient(top,#fff 0,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%);background:-o-linear-gradient(top,#fff 0,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%);background:-ms-linear-gradient(top,#fff 0,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%);background:linear-gradient(to bottom,#fff 0,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#fcfcfc', GradientType=0);width:600px;height:30px;z-index:10;position:absolute}div.graph-manipulation-editMode{height:30px;z-index:10;position:absolute;margin-top:20px}div.graph-manipulation-closeDiv{height:30px;width:30px;z-index:11;position:absolute;margin-top:3px;margin-left:590px;background-position:0 0;background-repeat:no-repeat;background-image:url(img/graph/cross.png);cursor:pointer;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}span.graph-manipulationUI{font-family:verdana;font-size:12px;-moz-border-radius:15px;border-radius:15px;display:inline-block;background-position:0 0;background-repeat:no-repeat;height:24px;margin:-14px 0 0 10px;vertical-align:middle;cursor:pointer;padding:0 8px;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}span.graph-manipulationUI:hover{box-shadow:1px 1px 8px rgba(0,0,0,.2)}span.graph-manipulationUI:active{box-shadow:1px 1px 8px rgba(0,0,0,.5)}span.graph-manipulationUI.back{background-image:url(img/graph/backIcon.png)}span.graph-manipulationUI.none:hover{box-shadow:1px 1px 8px rgba(0,0,0,0);cursor:default}span.graph-manipulationUI.none:active{box-shadow:1px 1px 8px rgba(0,0,0,0)}span.graph-manipulationUI.none{padding:0}span.graph-manipulationUI.notification{margin:2px;font-weight:700}span.graph-manipulationUI.add{background-image:url(img/graph/addNodeIcon.png)}span.graph-manipulationUI.edit{background-image:url(img/graph/editIcon.png)}span.graph-manipulationUI.edit.editmode{background-color:#fcfcfc;border-style:solid;border-width:1px;border-color:#ccc}span.graph-manipulationUI.connect{background-image:url(img/graph/connectIcon.png)}span.graph-manipulationUI.delete{background-image:url(img/graph/deleteIcon.png)}span.graph-manipulationLabel{margin:0 0 0 23px;line-height:25px}div.graph-seperatorLine{display:inline-block;width:1px;height:20px;background-color:#bdbdbd;margin:5px 7px 0 15px}div.graph-navigation{width:34px;height:34px;z-index:10;-moz-border-radius:17px;border-radius:17px;position:absolute;display:inline-block;background-position:2px 2px;background-repeat:no-repeat;cursor:pointer;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}div.graph-navigation:hover{box-shadow:0 0 3px 3px rgba(56,207,21,.3)}div.graph-navigation.active,div.graph-navigation:active{box-shadow:0 0 1px 3px rgba(56,207,21,.95)}div.graph-navigation.up{background-image:url(img/graph/upArrow.png);bottom:50px;left:55px}div.graph-navigation.down{background-image:url(img/graph/downArrow.png);bottom:10px;left:55px}div.graph-navigation.left{background-image:url(img/graph/leftArrow.png);bottom:10px;left:15px}div.graph-navigation.right{background-image:url(img/graph/rightArrow.png);bottom:10px;left:95px}div.graph-navigation.zoomIn{background-image:url(img/graph/plus.png);bottom:10px;right:15px}div.graph-navigation.zoomOut{background-image:url(img/graph/minus.png);bottom:10px;right:55px}div.graph-navigation.zoomExtends{background-image:url(img/graph/zoomExtends.png);bottom:50px;right:15px} \ No newline at end of file diff --git a/web/dist/vis.min.js b/web/dist/vis.min.js new file mode 100644 index 0000000..b121910 --- /dev/null +++ b/web/dist/vis.min.js @@ -0,0 +1,33 @@ +/** + * vis.js + * https://github.com/almende/vis + * + * A dynamic, browser-based visualization library. + * + * @version 0.7.1 + * @date 2014-03-27 + * + * @license + * Copyright (C) 2011-2014 Almende B.V, http://almende.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +!function(t){if("object"==typeof exports)module.exports=t();else if("function"==typeof define&&define.amd)define(t);else{var e;"undefined"!=typeof window?e=window:"undefined"!=typeof global?e=global:"undefined"!=typeof self&&(e=self),e.vis=t()}}(function(){var define,module,exports;return function t(e,i,s){function n(r,a){if(!i[r]){if(!e[r]){var h="function"==typeof require&&require;if(!a&&h)return h(r,!0);if(o)return o(r,!0);throw new Error("Cannot find module '"+r+"'")}var d=i[r]={exports:{}};e[r][0].call(d.exports,function(t){var i=e[r][1][t];return n(i?i:t)},d,d.exports,t,e,i,s)}return i[r].exports}for(var o="function"==typeof require&&require,r=0;ri;++i)t.call(e||this,this[i],i,this)}),Array.prototype.map||(Array.prototype.map=function(t,e){var i,s,n;if(null==this)throw new TypeError(" this is null or not defined");var o=Object(this),r=o.length>>>0;if("function"!=typeof t)throw new TypeError(t+" is not a function");for(e&&(i=e),s=new Array(r),n=0;r>n;){var a,h;n in o&&(a=o[n],h=t.call(i,a,n,o),s[n]=h),n++}return s}),Array.prototype.filter||(Array.prototype.filter=function(t){"use strict";if(null==this)throw new TypeError;var e=Object(this),i=e.length>>>0;if("function"!=typeof t)throw new TypeError;for(var s=[],n=arguments[1],o=0;i>o;o++)if(o in e){var r=e[o];t.call(n,r,o,e)&&s.push(r)}return s}),Object.keys||(Object.keys=function(){var t=Object.prototype.hasOwnProperty,e=!{toString:null}.propertyIsEnumerable("toString"),i=["toString","toLocaleString","valueOf","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","constructor"],s=i.length;return function(n){if("object"!=typeof n&&"function"!=typeof n||null===n)throw new TypeError("Object.keys called on non-object");var o=[];for(var r in n)t.call(n,r)&&o.push(r);if(e)for(var a=0;s>a;a++)t.call(n,i[a])&&o.push(i[a]);return o}}()),Array.isArray||(Array.isArray=function(t){return"[object Array]"===Object.prototype.toString.call(t)}),Function.prototype.bind||(Function.prototype.bind=function(t){if("function"!=typeof this)throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");var e=Array.prototype.slice.call(arguments,1),i=this,s=function(){},n=function(){return i.apply(this instanceof s&&t?this:t,e.concat(Array.prototype.slice.call(arguments)))};return s.prototype=this.prototype,n.prototype=new s,n}),Object.create||(Object.create=function(t){function e(){}if(arguments.length>1)throw new Error("Object.create implementation only accepts the first parameter.");return e.prototype=t,new e}),Function.prototype.bind||(Function.prototype.bind=function(t){if("function"!=typeof this)throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");var e=Array.prototype.slice.call(arguments,1),i=this,s=function(){},n=function(){return i.apply(this instanceof s&&t?this:t,e.concat(Array.prototype.slice.call(arguments)))};return s.prototype=this.prototype,n.prototype=new s,n});var util={};util.isNumber=function(t){return t instanceof Number||"number"==typeof t},util.isString=function(t){return t instanceof String||"string"==typeof t},util.isDate=function(t){if(t instanceof Date)return!0;if(util.isString(t)){var e=ASPDateRegex.exec(t);if(e)return!0;if(!isNaN(Date.parse(t)))return!0}return!1},util.isDataTable=function(t){return"undefined"!=typeof google&&google.visualization&&google.visualization.DataTable&&t instanceof google.visualization.DataTable},util.randomUUID=function(){var t=function(){return Math.floor(65536*Math.random()).toString(16)};return t()+t()+"-"+t()+"-"+t()+"-"+t()+"-"+t()+t()+t()},util.extend=function(t){for(var e=1,i=arguments.length;i>e;e++){var s=arguments[e];for(var n in s)s.hasOwnProperty(n)&&void 0!==s[n]&&(t[n]=s[n])}return t},util.convert=function(t,e){var i;if(void 0===t)return void 0;if(null===t)return null;if(!e)return t;if("string"!=typeof e&&!(e instanceof String))throw new Error("Type must be a string");switch(e){case"boolean":case"Boolean":return Boolean(t);case"number":case"Number":return Number(t.valueOf());case"string":case"String":return String(t);case"Date":if(util.isNumber(t))return new Date(t);if(t instanceof Date)return new Date(t.valueOf());if(moment.isMoment(t))return new Date(t.valueOf());if(util.isString(t))return i=ASPDateRegex.exec(t),i?new Date(Number(i[1])):moment(t).toDate();throw new Error("Cannot convert object of type "+util.getType(t)+" to type Date");case"Moment":if(util.isNumber(t))return moment(t);if(t instanceof Date)return moment(t.valueOf());if(moment.isMoment(t))return moment(t);if(util.isString(t))return i=ASPDateRegex.exec(t),moment(i?Number(i[1]):t);throw new Error("Cannot convert object of type "+util.getType(t)+" to type Date");case"ISODate":if(util.isNumber(t))return new Date(t);if(t instanceof Date)return t.toISOString();if(moment.isMoment(t))return t.toDate().toISOString();if(util.isString(t))return i=ASPDateRegex.exec(t),i?new Date(Number(i[1])).toISOString():new Date(t).toISOString();throw new Error("Cannot convert object of type "+util.getType(t)+" to type ISODate");case"ASPDate":if(util.isNumber(t))return"/Date("+t+")/";if(t instanceof Date)return"/Date("+t.valueOf()+")/";if(util.isString(t)){i=ASPDateRegex.exec(t);var s;return s=i?new Date(Number(i[1])).valueOf():new Date(t).valueOf(),"/Date("+s+")/"}throw new Error("Cannot convert object of type "+util.getType(t)+" to type ASPDate");default:throw new Error("Cannot convert object of type "+util.getType(t)+' to type "'+e+'"')}};var ASPDateRegex=/^\/?Date\((\-?\d+)/i;util.getType=function(t){var e=typeof t;return"object"==e?null==t?"null":t instanceof Boolean?"Boolean":t instanceof Number?"Number":t instanceof String?"String":t instanceof Array?"Array":t instanceof Date?"Date":"Object":"number"==e?"Number":"boolean"==e?"Boolean":"string"==e?"String":e},util.getAbsoluteLeft=function(t){for(var e=document.documentElement,i=document.body,s=t.offsetLeft,n=t.offsetParent;null!=n&&n!=i&&n!=e;)s+=n.offsetLeft,s-=n.scrollLeft,n=n.offsetParent;return s},util.getAbsoluteTop=function(t){for(var e=document.documentElement,i=document.body,s=t.offsetTop,n=t.offsetParent;null!=n&&n!=i&&n!=e;)s+=n.offsetTop,s-=n.scrollTop,n=n.offsetParent;return s},util.getPageY=function(t){if("pageY"in t)return t.pageY;var e;e="targetTouches"in t&&t.targetTouches.length?t.targetTouches[0].clientY:t.clientY;var i=document.documentElement,s=document.body;return e+(i&&i.scrollTop||s&&s.scrollTop||0)-(i&&i.clientTop||s&&s.clientTop||0)},util.getPageX=function(t){if("pageY"in t)return t.pageX;var e;e="targetTouches"in t&&t.targetTouches.length?t.targetTouches[0].clientX:t.clientX;var i=document.documentElement,s=document.body;return e+(i&&i.scrollLeft||s&&s.scrollLeft||0)-(i&&i.clientLeft||s&&s.clientLeft||0)},util.addClassName=function(t,e){var i=t.className.split(" ");-1==i.indexOf(e)&&(i.push(e),t.className=i.join(" "))},util.removeClassName=function(t,e){var i=t.className.split(" "),s=i.indexOf(e);-1!=s&&(i.splice(s,1),t.className=i.join(" "))},util.forEach=function(t,e){var i,s;if(t instanceof Array)for(i=0,s=t.length;s>i;i++)e(t[i],i,t);else for(i in t)t.hasOwnProperty(i)&&e(t[i],i,t)},util.updateProperty=function(t,e,i){return t[e]!==i?(t[e]=i,!0):!1},util.addEventListener=function(t,e,i,s){t.addEventListener?(void 0===s&&(s=!1),"mousewheel"===e&&navigator.userAgent.indexOf("Firefox")>=0&&(e="DOMMouseScroll"),t.addEventListener(e,i,s)):t.attachEvent("on"+e,i)},util.removeEventListener=function(t,e,i,s){t.removeEventListener?(void 0===s&&(s=!1),"mousewheel"===e&&navigator.userAgent.indexOf("Firefox")>=0&&(e="DOMMouseScroll"),t.removeEventListener(e,i,s)):t.detachEvent("on"+e,i)},util.getTarget=function(t){t||(t=window.event);var e;return t.target?e=t.target:t.srcElement&&(e=t.srcElement),void 0!=e.nodeType&&3==e.nodeType&&(e=e.parentNode),e},util.fakeGesture=function(t,e){var i=null,s=Hammer.event.collectEventData(this,i,e);return isNaN(s.center.pageX)&&(s.center.pageX=e.pageX),isNaN(s.center.pageY)&&(s.center.pageY=e.pageY),s},util.option={},util.option.asBoolean=function(t,e){return"function"==typeof t&&(t=t()),null!=t?0!=t:e||null},util.option.asNumber=function(t,e){return"function"==typeof t&&(t=t()),null!=t?Number(t)||e||null:e||null},util.option.asString=function(t,e){return"function"==typeof t&&(t=t()),null!=t?String(t):e||null},util.option.asSize=function(t,e){return"function"==typeof t&&(t=t()),util.isString(t)?t:util.isNumber(t)?t+"px":e||null},util.option.asElement=function(t,e){return"function"==typeof t&&(t=t()),t||e||null},util.GiveDec=function GiveDec(Hex){return Value="A"==Hex?10:"B"==Hex?11:"C"==Hex?12:"D"==Hex?13:"E"==Hex?14:"F"==Hex?15:eval(Hex)},util.GiveHex=function(t){return Value=10==t?"A":11==t?"B":12==t?"C":13==t?"D":14==t?"E":15==t?"F":""+t},util.hexToRGB=function(t){t=t.replace("#","").toUpperCase();var e=util.GiveDec(t.substring(0,1)),i=util.GiveDec(t.substring(1,2)),s=util.GiveDec(t.substring(2,3)),n=util.GiveDec(t.substring(3,4)),o=util.GiveDec(t.substring(4,5)),r=util.GiveDec(t.substring(5,6)),a=16*e+i,h=16*s+n,i=16*o+r;return{r:a,g:h,b:i}},util.RGBToHex=function(t,e,i){var s=util.GiveHex(Math.floor(t/16)),n=util.GiveHex(t%16),o=util.GiveHex(Math.floor(e/16)),r=util.GiveHex(e%16),a=util.GiveHex(Math.floor(i/16)),h=util.GiveHex(i%16),d=s+n+o+r+a+h;return"#"+d},util.RGBToHSV=function(t,e,i){t/=255,e/=255,i/=255;var s=Math.min(t,Math.min(e,i)),n=Math.max(t,Math.max(e,i));if(s==n)return{h:0,s:0,v:s};var o=t==s?e-i:i==s?t-e:i-t,r=t==s?3:i==s?1:5,a=60*(r-o/(n-s))/360,h=(n-s)/n,d=n;return{h:a,s:h,v:d}},util.HSVToRGB=function(t,e,i){var s,n,o,r=Math.floor(6*t),a=6*t-r,h=i*(1-e),d=i*(1-a*e),c=i*(1-(1-a)*e);switch(r%6){case 0:s=i,n=c,o=h;break;case 1:s=d,n=i,o=h;break;case 2:s=h,n=i,o=c;break;case 3:s=h,n=d,o=i;break;case 4:s=c,n=h,o=i;break;case 5:s=i,n=h,o=d}return{r:Math.floor(255*s),g:Math.floor(255*n),b:Math.floor(255*o)}},util.HSVToHex=function(t,e,i){var s=util.HSVToRGB(t,e,i);return util.RGBToHex(s.r,s.g,s.b)},util.hexToHSV=function(t){var e=util.hexToRGB(t);return util.RGBToHSV(e.r,e.g,e.b)},util.isValidHex=function(t){var e=/(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(t);return e},util.copyObject=function(t,e){for(var i in t)t.hasOwnProperty(i)&&("object"==typeof t[i]?(e[i]={},util.copyObject(t[i],e[i])):e[i]=t[i])},DataSet.prototype.on=function(t,e){var i=this.subscribers[t];i||(i=[],this.subscribers[t]=i),i.push({callback:e})},DataSet.prototype.subscribe=DataSet.prototype.on,DataSet.prototype.off=function(t,e){var i=this.subscribers[t];i&&(this.subscribers[t]=i.filter(function(t){return t.callback!=e}))},DataSet.prototype.unsubscribe=DataSet.prototype.off,DataSet.prototype._trigger=function(t,e,i){if("*"==t)throw new Error("Cannot trigger event *");var s=[];t in this.subscribers&&(s=s.concat(this.subscribers[t])),"*"in this.subscribers&&(s=s.concat(this.subscribers["*"]));for(var n=0;no;o++)i=n._addItem(t[o]),s.push(i);else if(util.isDataTable(t))for(var a=this._getColumnNames(t),h=0,d=t.getNumberOfRows();d>h;h++){for(var c={},l=0,u=a.length;u>l;l++){var p=a[l]; +c[p]=t.getValue(h,l)}i=n._addItem(c),s.push(i)}else{if(!(t instanceof Object))throw new Error("Unknown dataType");i=n._addItem(t),s.push(i)}return s.length&&this._trigger("add",{items:s},e),s},DataSet.prototype.update=function(t,e){var i=[],s=[],n=this,o=n.fieldId,r=function(t){var e=t[o];n.data[e]?(e=n._updateItem(t),s.push(e)):(e=n._addItem(t),i.push(e))};if(t instanceof Array)for(var a=0,h=t.length;h>a;a++)r(t[a]);else if(util.isDataTable(t))for(var d=this._getColumnNames(t),c=0,l=t.getNumberOfRows();l>c;c++){for(var u={},p=0,g=d.length;g>p;p++){var f=d[p];u[f]=t.getValue(c,p)}r(u)}else{if(!(t instanceof Object))throw new Error("Unknown dataType");r(t)}return i.length&&this._trigger("add",{items:i},e),s.length&&this._trigger("update",{items:s},e),i.concat(s)},DataSet.prototype.get=function(){var t,e,i,s,n=this,o=this.showInternalIds,r=util.getType(arguments[0]);"String"==r||"Number"==r?(t=arguments[0],i=arguments[1],s=arguments[2]):"Array"==r?(e=arguments[0],i=arguments[1],s=arguments[2]):(i=arguments[0],s=arguments[1]);var a;if(i&&i.type){if(a="DataTable"==i.type?"DataTable":"Array",s&&a!=util.getType(s))throw new Error('Type of parameter "data" ('+util.getType(s)+") does not correspond with specified options.type ("+i.type+")");if("DataTable"==a&&!util.isDataTable(s))throw new Error('Parameter "data" must be a DataTable when options.type is "DataTable"')}else a=s&&"DataTable"==util.getType(s)?"DataTable":"Array";void 0!=i&&void 0!=i.showInternalIds&&(this.showInternalIds=i.showInternalIds);var h,d,c,l,u=i&&i.convert||this.options.convert,p=i&&i.filter,g=[];if(void 0!=t)h=n._getItem(t,u),p&&!p(h)&&(h=null);else if(void 0!=e)for(c=0,l=e.length;l>c;c++)h=n._getItem(e[c],u),(!p||p(h))&&g.push(h);else for(d in this.data)this.data.hasOwnProperty(d)&&(h=n._getItem(d,u),(!p||p(h))&&g.push(h));if(this.showInternalIds=o,i&&i.order&&void 0==t&&this._sort(g,i.order),i&&i.fields){var f=i.fields;if(void 0!=t)h=this._filterFields(h,f);else for(c=0,l=g.length;l>c;c++)g[c]=this._filterFields(g[c],f)}if("DataTable"==a){var m=this._getColumnNames(s);if(void 0!=t)n._appendRow(s,m,h);else for(c=0,l=g.length;l>c;c++)n._appendRow(s,m,g[c]);return s}if(void 0!=t)return h;if(s){for(c=0,l=g.length;l>c;c++)s.push(g[c]);return s}return g},DataSet.prototype.getIds=function(t){var e,i,s,n,o,r=this.data,a=t&&t.filter,h=t&&t.order,d=t&&t.convert||this.options.convert,c=[];if(a)if(h){o=[];for(s in r)r.hasOwnProperty(s)&&(n=this._getItem(s,d),a(n)&&o.push(n));for(this._sort(o,h),e=0,i=o.length;i>e;e++)c[e]=o[e][this.fieldId]}else for(s in r)r.hasOwnProperty(s)&&(n=this._getItem(s,d),a(n)&&c.push(n[this.fieldId]));else if(h){o=[];for(s in r)r.hasOwnProperty(s)&&o.push(r[s]);for(this._sort(o,h),e=0,i=o.length;i>e;e++)c[e]=o[e][this.fieldId]}else for(s in r)r.hasOwnProperty(s)&&(n=r[s],c.push(n[this.fieldId]));return c},DataSet.prototype.forEach=function(t,e){var i,s,n=e&&e.filter,o=e&&e.convert||this.options.convert,r=this.data;if(e&&e.order)for(var a=this.get(e),h=0,d=a.length;d>h;h++)i=a[h],s=i[this.fieldId],t(i,s);else for(s in r)r.hasOwnProperty(s)&&(i=this._getItem(s,o),(!n||n(i))&&t(i,s))},DataSet.prototype.map=function(t,e){var i,s=e&&e.filter,n=e&&e.convert||this.options.convert,o=[],r=this.data;for(var a in r)r.hasOwnProperty(a)&&(i=this._getItem(a,n),(!s||s(i))&&o.push(t(i,a)));return e&&e.order&&this._sort(o,e.order),o},DataSet.prototype._filterFields=function(t,e){var i={};for(var s in t)t.hasOwnProperty(s)&&-1!=e.indexOf(s)&&(i[s]=t[s]);return i},DataSet.prototype._sort=function(t,e){if(util.isString(e)){var i=e;t.sort(function(t,e){var s=t[i],n=e[i];return s>n?1:n>s?-1:0})}else{if("function"!=typeof e)throw new TypeError("Order must be a function or a string");t.sort(e)}},DataSet.prototype.remove=function(t,e){var i,s,n,o=[];if(t instanceof Array)for(i=0,s=t.length;s>i;i++)n=this._remove(t[i]),null!=n&&o.push(n);else n=this._remove(t),null!=n&&o.push(n);return o.length&&this._trigger("remove",{items:o},e),o},DataSet.prototype._remove=function(t){if(util.isNumber(t)||util.isString(t)){if(this.data[t])return delete this.data[t],delete this.internalIds[t],t}else if(t instanceof Object){var e=t[this.fieldId];if(e&&this.data[e])return delete this.data[e],delete this.internalIds[e],e}return null},DataSet.prototype.clear=function(t){var e=Object.keys(this.data);return this.data={},this.internalIds={},this._trigger("remove",{items:e},t),e},DataSet.prototype.max=function(t){var e=this.data,i=null,s=null;for(var n in e)if(e.hasOwnProperty(n)){var o=e[n],r=o[t];null!=r&&(!i||r>s)&&(i=o,s=r)}return i},DataSet.prototype.min=function(t){var e=this.data,i=null,s=null;for(var n in e)if(e.hasOwnProperty(n)){var o=e[n],r=o[t];null!=r&&(!i||s>r)&&(i=o,s=r)}return i},DataSet.prototype.distinct=function(t){var e=this.data,i=[],s=this.options.convert[t],n=0;for(var o in e)if(e.hasOwnProperty(o)){for(var r=e[o],a=util.convert(r[t],s),h=!1,d=0;n>d;d++)if(i[d]==a){h=!0;break}h||(i[n]=a,n++)}return i},DataSet.prototype._addItem=function(t){var e=t[this.fieldId];if(void 0!=e){if(this.data[e])throw new Error("Cannot add item: item with id "+e+" already exists")}else e=util.randomUUID(),t[this.fieldId]=e,this.internalIds[e]=t;var i={};for(var s in t)if(t.hasOwnProperty(s)){var n=this.convert[s];i[s]=util.convert(t[s],n)}return this.data[e]=i,e},DataSet.prototype._getItem=function(t,e){var i,s,n=this.data[t];if(!n)return null;var o={},r=this.fieldId,a=this.internalIds;if(e)for(i in n)n.hasOwnProperty(i)&&(s=n[i],i==r&&s in a&&!this.showInternalIds||(o[i]=util.convert(s,e[i])));else for(i in n)n.hasOwnProperty(i)&&(s=n[i],i==r&&s in a&&!this.showInternalIds||(o[i]=s));return o},DataSet.prototype._updateItem=function(t){var e=t[this.fieldId];if(void 0==e)throw new Error("Cannot update item: item has no id (item: "+JSON.stringify(t)+")");var i=this.data[e];if(!i)throw new Error("Cannot update item: no item with id "+e+" found");for(var s in t)if(t.hasOwnProperty(s)){var n=this.convert[s];i[s]=util.convert(t[s],n)}return e},DataSet.prototype.isInternalId=function(t){return t in this.internalIds},DataSet.prototype._getColumnNames=function(t){for(var e=[],i=0,s=t.getNumberOfColumns();s>i;i++)e[i]=t.getColumnId(i)||t.getColumnLabel(i);return e},DataSet.prototype._appendRow=function(t,e,i){for(var s=t.addRow(),n=0,o=e.length;o>n;n++){var r=e[n];t.setValue(s,n,i[r])}},DataView.prototype.setData=function(t){var e,i,s;if(this.data){this.data.unsubscribe&&this.data.unsubscribe("*",this.listener),e=[];for(var n in this.ids)this.ids.hasOwnProperty(n)&&e.push(n);this.ids={},this._trigger("remove",{items:e})}if(this.data=t,this.data){for(this.fieldId=this.options.fieldId||this.data&&this.data.options&&this.data.options.fieldId||"id",e=this.data.getIds({filter:this.options&&this.options.filter}),i=0,s=e.length;s>i;i++)n=e[i],this.ids[n]=!0;this._trigger("add",{items:e}),this.data.on&&this.data.on("*",this.listener)}},DataView.prototype.get=function(){var t,e,i,s=this,n=util.getType(arguments[0]);"String"==n||"Number"==n||"Array"==n?(t=arguments[0],e=arguments[1],i=arguments[2]):(e=arguments[0],i=arguments[1]);var o=util.extend({},this.options,e);this.options.filter&&e&&e.filter&&(o.filter=function(t){return s.options.filter(t)&&e.filter(t)});var r=[];return void 0!=t&&r.push(t),r.push(o),r.push(i),this.data&&this.data.get.apply(this.data,r)},DataView.prototype.getIds=function(t){var e;if(this.data){var i,s=this.options.filter;i=t&&t.filter?s?function(e){return s(e)&&t.filter(e)}:t.filter:s,e=this.data.getIds({filter:i,order:t&&t.order})}else e=[];return e},DataView.prototype._onEvent=function(t,e,i){var s,n,o,r,a=e&&e.items,h=this.data,d=[],c=[],l=[];if(a&&h){switch(t){case"add":for(s=0,n=a.length;n>s;s++)o=a[s],r=this.get(o),r&&(this.ids[o]=!0,d.push(o));break;case"update":for(s=0,n=a.length;n>s;s++)o=a[s],r=this.get(o),r?this.ids[o]?c.push(o):(this.ids[o]=!0,d.push(o)):this.ids[o]&&(delete this.ids[o],l.push(o));break;case"remove":for(s=0,n=a.length;n>s;s++)o=a[s],this.ids[o]&&(delete this.ids[o],l.push(o))}d.length&&this._trigger("add",{items:d},i),c.length&&this._trigger("update",{items:c},i),l.length&&this._trigger("remove",{items:l},i)}},DataView.prototype.on=DataSet.prototype.on,DataView.prototype.off=DataSet.prototype.off,DataView.prototype._trigger=DataSet.prototype._trigger,DataView.prototype.subscribe=DataView.prototype.on,DataView.prototype.unsubscribe=DataView.prototype.off,TimeStep=function(t,e,i){this.current=new Date,this._start=new Date,this._end=new Date,this.autoScale=!0,this.scale=TimeStep.SCALE.DAY,this.step=1,this.setRange(t,e,i)},TimeStep.SCALE={MILLISECOND:1,SECOND:2,MINUTE:3,HOUR:4,DAY:5,WEEKDAY:6,MONTH:7,YEAR:8},TimeStep.prototype.setRange=function(t,e,i){if(!(t instanceof Date&&e instanceof Date))throw"No legal start or end date in method setRange";this._start=void 0!=t?new Date(t.valueOf()):new Date,this._end=void 0!=e?new Date(e.valueOf()):new Date,this.autoScale&&this.setMinimumStep(i)},TimeStep.prototype.first=function(){this.current=new Date(this._start.valueOf()),this.roundToMinor()},TimeStep.prototype.roundToMinor=function(){switch(this.scale){case TimeStep.SCALE.YEAR:this.current.setFullYear(this.step*Math.floor(this.current.getFullYear()/this.step)),this.current.setMonth(0);case TimeStep.SCALE.MONTH:this.current.setDate(1);case TimeStep.SCALE.DAY:case TimeStep.SCALE.WEEKDAY:this.current.setHours(0);case TimeStep.SCALE.HOUR:this.current.setMinutes(0);case TimeStep.SCALE.MINUTE:this.current.setSeconds(0);case TimeStep.SCALE.SECOND:this.current.setMilliseconds(0)}if(1!=this.step)switch(this.scale){case TimeStep.SCALE.MILLISECOND:this.current.setMilliseconds(this.current.getMilliseconds()-this.current.getMilliseconds()%this.step);break;case TimeStep.SCALE.SECOND:this.current.setSeconds(this.current.getSeconds()-this.current.getSeconds()%this.step);break;case TimeStep.SCALE.MINUTE:this.current.setMinutes(this.current.getMinutes()-this.current.getMinutes()%this.step);break;case TimeStep.SCALE.HOUR:this.current.setHours(this.current.getHours()-this.current.getHours()%this.step);break;case TimeStep.SCALE.WEEKDAY:case TimeStep.SCALE.DAY:this.current.setDate(this.current.getDate()-1-(this.current.getDate()-1)%this.step+1);break;case TimeStep.SCALE.MONTH:this.current.setMonth(this.current.getMonth()-this.current.getMonth()%this.step);break;case TimeStep.SCALE.YEAR:this.current.setFullYear(this.current.getFullYear()-this.current.getFullYear()%this.step)}},TimeStep.prototype.hasNext=function(){return this.current.valueOf()<=this._end.valueOf()},TimeStep.prototype.next=function(){var t=this.current.valueOf();if(this.current.getMonth()<6)switch(this.scale){case TimeStep.SCALE.MILLISECOND:this.current=new Date(this.current.valueOf()+this.step);break;case TimeStep.SCALE.SECOND:this.current=new Date(this.current.valueOf()+1e3*this.step);break;case TimeStep.SCALE.MINUTE:this.current=new Date(this.current.valueOf()+1e3*this.step*60);break;case TimeStep.SCALE.HOUR:this.current=new Date(this.current.valueOf()+1e3*this.step*60*60);var e=this.current.getHours();this.current.setHours(e-e%this.step);break;case TimeStep.SCALE.WEEKDAY:case TimeStep.SCALE.DAY:this.current.setDate(this.current.getDate()+this.step);break;case TimeStep.SCALE.MONTH:this.current.setMonth(this.current.getMonth()+this.step);break;case TimeStep.SCALE.YEAR:this.current.setFullYear(this.current.getFullYear()+this.step)}else switch(this.scale){case TimeStep.SCALE.MILLISECOND:this.current=new Date(this.current.valueOf()+this.step);break;case TimeStep.SCALE.SECOND:this.current.setSeconds(this.current.getSeconds()+this.step);break;case TimeStep.SCALE.MINUTE:this.current.setMinutes(this.current.getMinutes()+this.step);break;case TimeStep.SCALE.HOUR:this.current.setHours(this.current.getHours()+this.step);break;case TimeStep.SCALE.WEEKDAY:case TimeStep.SCALE.DAY:this.current.setDate(this.current.getDate()+this.step);break;case TimeStep.SCALE.MONTH:this.current.setMonth(this.current.getMonth()+this.step);break;case TimeStep.SCALE.YEAR:this.current.setFullYear(this.current.getFullYear()+this.step)}if(1!=this.step)switch(this.scale){case TimeStep.SCALE.MILLISECOND:this.current.getMilliseconds()0&&(this.step=e),this.autoScale=!1},TimeStep.prototype.setAutoScale=function(t){this.autoScale=t},TimeStep.prototype.setMinimumStep=function(t){if(void 0!=t){var e=31104e6,i=2592e6,s=864e5,n=36e5,o=6e4,r=1e3,a=1;1e3*e>t&&(this.scale=TimeStep.SCALE.YEAR,this.step=1e3),500*e>t&&(this.scale=TimeStep.SCALE.YEAR,this.step=500),100*e>t&&(this.scale=TimeStep.SCALE.YEAR,this.step=100),50*e>t&&(this.scale=TimeStep.SCALE.YEAR,this.step=50),10*e>t&&(this.scale=TimeStep.SCALE.YEAR,this.step=10),5*e>t&&(this.scale=TimeStep.SCALE.YEAR,this.step=5),e>t&&(this.scale=TimeStep.SCALE.YEAR,this.step=1),3*i>t&&(this.scale=TimeStep.SCALE.MONTH,this.step=3),i>t&&(this.scale=TimeStep.SCALE.MONTH,this.step=1),5*s>t&&(this.scale=TimeStep.SCALE.DAY,this.step=5),2*s>t&&(this.scale=TimeStep.SCALE.DAY,this.step=2),s>t&&(this.scale=TimeStep.SCALE.DAY,this.step=1),s/2>t&&(this.scale=TimeStep.SCALE.WEEKDAY,this.step=1),4*n>t&&(this.scale=TimeStep.SCALE.HOUR,this.step=4),n>t&&(this.scale=TimeStep.SCALE.HOUR,this.step=1),15*o>t&&(this.scale=TimeStep.SCALE.MINUTE,this.step=15),10*o>t&&(this.scale=TimeStep.SCALE.MINUTE,this.step=10),5*o>t&&(this.scale=TimeStep.SCALE.MINUTE,this.step=5),o>t&&(this.scale=TimeStep.SCALE.MINUTE,this.step=1),15*r>t&&(this.scale=TimeStep.SCALE.SECOND,this.step=15),10*r>t&&(this.scale=TimeStep.SCALE.SECOND,this.step=10),5*r>t&&(this.scale=TimeStep.SCALE.SECOND,this.step=5),r>t&&(this.scale=TimeStep.SCALE.SECOND,this.step=1),200*a>t&&(this.scale=TimeStep.SCALE.MILLISECOND,this.step=200),100*a>t&&(this.scale=TimeStep.SCALE.MILLISECOND,this.step=100),50*a>t&&(this.scale=TimeStep.SCALE.MILLISECOND,this.step=50),10*a>t&&(this.scale=TimeStep.SCALE.MILLISECOND,this.step=10),5*a>t&&(this.scale=TimeStep.SCALE.MILLISECOND,this.step=5),a>t&&(this.scale=TimeStep.SCALE.MILLISECOND,this.step=1)}},TimeStep.prototype.snap=function(t){var e=new Date(t.valueOf());if(this.scale==TimeStep.SCALE.YEAR){var i=e.getFullYear()+Math.round(e.getMonth()/12);e.setFullYear(Math.round(i/this.step)*this.step),e.setMonth(0),e.setDate(0),e.setHours(0),e.setMinutes(0),e.setSeconds(0),e.setMilliseconds(0)}else if(this.scale==TimeStep.SCALE.MONTH)e.getDate()>15?(e.setDate(1),e.setMonth(e.getMonth()+1)):e.setDate(1),e.setHours(0),e.setMinutes(0),e.setSeconds(0),e.setMilliseconds(0);else if(this.scale==TimeStep.SCALE.DAY||this.scale==TimeStep.SCALE.WEEKDAY){switch(this.step){case 5:case 2:e.setHours(24*Math.round(e.getHours()/24));break;default:e.setHours(12*Math.round(e.getHours()/12))}e.setMinutes(0),e.setSeconds(0),e.setMilliseconds(0)}else if(this.scale==TimeStep.SCALE.HOUR){switch(this.step){case 4:e.setMinutes(60*Math.round(e.getMinutes()/60));break;default:e.setMinutes(30*Math.round(e.getMinutes()/30))}e.setSeconds(0),e.setMilliseconds(0)}else if(this.scale==TimeStep.SCALE.MINUTE){switch(this.step){case 15:case 10:e.setMinutes(5*Math.round(e.getMinutes()/5)),e.setSeconds(0);break;case 5:e.setSeconds(60*Math.round(e.getSeconds()/60));break;default:e.setSeconds(30*Math.round(e.getSeconds()/30))}e.setMilliseconds(0)}else if(this.scale==TimeStep.SCALE.SECOND)switch(this.step){case 15:case 10:e.setSeconds(5*Math.round(e.getSeconds()/5)),e.setMilliseconds(0);break;case 5:e.setMilliseconds(1e3*Math.round(e.getMilliseconds()/1e3));break;default:e.setMilliseconds(500*Math.round(e.getMilliseconds()/500))}else if(this.scale==TimeStep.SCALE.MILLISECOND){var s=this.step>5?this.step/2:1;e.setMilliseconds(Math.round(e.getMilliseconds()/s)*s)}return e},TimeStep.prototype.isMajor=function(){switch(this.scale){case TimeStep.SCALE.MILLISECOND:return 0==this.current.getMilliseconds();case TimeStep.SCALE.SECOND:return 0==this.current.getSeconds();case TimeStep.SCALE.MINUTE:return 0==this.current.getHours()&&0==this.current.getMinutes();case TimeStep.SCALE.HOUR:return 0==this.current.getHours();case TimeStep.SCALE.WEEKDAY:case TimeStep.SCALE.DAY:return 1==this.current.getDate();case TimeStep.SCALE.MONTH:return 0==this.current.getMonth();case TimeStep.SCALE.YEAR:return!1;default:return!1}},TimeStep.prototype.getLabelMinor=function(t){switch(void 0==t&&(t=this.current),this.scale){case TimeStep.SCALE.MILLISECOND:return moment(t).format("SSS");case TimeStep.SCALE.SECOND:return moment(t).format("s");case TimeStep.SCALE.MINUTE:return moment(t).format("HH:mm");case TimeStep.SCALE.HOUR:return moment(t).format("HH:mm");case TimeStep.SCALE.WEEKDAY:return moment(t).format("ddd D");case TimeStep.SCALE.DAY:return moment(t).format("D");case TimeStep.SCALE.MONTH:return moment(t).format("MMM");case TimeStep.SCALE.YEAR:return moment(t).format("YYYY");default:return""}},TimeStep.prototype.getLabelMajor=function(t){switch(void 0==t&&(t=this.current),this.scale){case TimeStep.SCALE.MILLISECOND:return moment(t).format("HH:mm:ss");case TimeStep.SCALE.SECOND:return moment(t).format("D MMMM HH:mm");case TimeStep.SCALE.MINUTE:case TimeStep.SCALE.HOUR:return moment(t).format("ddd D MMMM");case TimeStep.SCALE.WEEKDAY:case TimeStep.SCALE.DAY:return moment(t).format("MMMM YYYY");case TimeStep.SCALE.MONTH:return moment(t).format("YYYY");case TimeStep.SCALE.YEAR:return"";default:return""}},Stack.prototype.setOptions=function(t){util.extend(this.options,t)},Stack.prototype.update=function(){this._order(),this._stack()},Stack.prototype._order=function(){var t=this.itemset.items;if(!t)throw new Error("Cannot stack items: ItemSet does not contain items");var e=[],i=0;util.forEach(t,function(t){t.visible&&(e[i]=t,i++)});var s=this.options.order||this.defaultOptions.order;if("function"!=typeof s)throw new Error("Option order must be a function");e.sort(s),this.ordered=e},Stack.prototype._stack=function(){var t,e,i,s=this.ordered,n=this.options,o=n.orientation||this.defaultOptions.orientation,r="top"==o;for(i=n.margin&&void 0!==n.margin.item?n.margin.item:this.defaultOptions.margin.item,t=0,e=s.length;e>t;t++){var a=s[t],h=null;do h=this.checkOverlap(s,t,0,t-1,i),null!=h&&(a.top=r?h.top+h.height+i:h.top-a.height-i);while(h)}},Stack.prototype.checkOverlap=function(t,e,i,s,n){for(var o=this.collision,r=t[e],a=s;a>=i;a--){var h=t[a];if(o(r,h,n)&&a!=e)return h}return null},Stack.prototype.collision=function(t,e,i){return t.left-ie.left&&t.top-ie.top},Emitter(Range.prototype),Range.prototype.setOptions=function(t){util.extend(this.options,t),null!==this.start&&null!==this.end&&this.setRange(this.start,this.end)},Range.prototype.subscribe=function(t,e,i,s){function n(t){o._onMouseWheel(t,e,s)}var o=this;if("move"==i)t.on("dragstart",function(t){o._onDragStart(t,e)}),t.on("drag",function(t){o._onDrag(t,e,s)}),t.on("dragend",function(t){o._onDragEnd(t,e)}),t.on("hold",function(){o._onHold()});else{if("zoom"!=i)throw new TypeError('Unknown event "'+i+'". Choose "move" or "zoom".');t.on("mousewheel",n),t.on("DOMMouseScroll",n),t.on("touch",function(t){o._onTouch(t)}),t.on("pinch",function(t){o._onPinch(t,e,s)})}},Range.prototype.setRange=function(t,e){var i=this._applyRange(t,e);if(i){var s={start:this.start,end:this.end};this.emit("rangechange",s),this.emit("rangechanged",s)}},Range.prototype._applyRange=function(t,e){var i,s=null!=t?util.convert(t,"Date").valueOf():this.start,n=null!=e?util.convert(e,"Date").valueOf():this.end,o=null!=this.options.max?util.convert(this.options.max,"Date").valueOf():null,r=null!=this.options.min?util.convert(this.options.min,"Date").valueOf():null;if(isNaN(s)||null===s)throw new Error('Invalid start "'+t+'"');if(isNaN(n)||null===n)throw new Error('Invalid end "'+e+'"');if(s>n&&(n=s),null!==r&&r>s&&(i=r-s,s+=i,n+=i,null!=o&&n>o&&(n=o)),null!==o&&n>o&&(i=n-o,s-=i,n-=i,null!=r&&r>s&&(s=r)),null!==this.options.zoomMin){var a=parseFloat(this.options.zoomMin);0>a&&(a=0),a>n-s&&(this.end-this.start===a?(s=this.start,n=this.end):(i=a-(n-s),s-=i/2,n+=i/2))}if(null!==this.options.zoomMax){var h=parseFloat(this.options.zoomMax);0>h&&(h=0),n-s>h&&(this.end-this.start===h?(s=this.start,n=this.end):(i=n-s-h,s+=i/2,n-=i/2))}var d=this.start!=s||this.end!=n;return this.start=s,this.end=n,d},Range.prototype.getRange=function(){return{start:this.start,end:this.end}},Range.prototype.conversion=function(t){return Range.conversion(this.start,this.end,t)},Range.conversion=function(t,e,i){return 0!=i&&e-t!=0?{offset:t,scale:i/(e-t)}:{offset:0,scale:1}};var touchParams={};Range.prototype._onDragStart=function(t,e){if(!touchParams.ignore){touchParams.start=this.start,touchParams.end=this.end;var i=e.frame;i&&(i.style.cursor="move")}},Range.prototype._onDrag=function(t,e,i){if(validateDirection(i),!touchParams.ignore){var s="horizontal"==i?t.gesture.deltaX:t.gesture.deltaY,n=touchParams.end-touchParams.start,o="horizontal"==i?e.width:e.height,r=-s/o*n;this._applyRange(touchParams.start+r,touchParams.end+r),this.emit("rangechange",{start:this.start,end:this.end})}},Range.prototype._onDragEnd=function(t,e){touchParams.ignore||(e.frame&&(e.frame.style.cursor="auto"),this.emit("rangechanged",{start:this.start,end:this.end}))},Range.prototype._onMouseWheel=function(t,e,i){validateDirection(i);var s=0;if(t.wheelDelta?s=t.wheelDelta/120:t.detail&&(s=-t.detail/3),s){var n;n=0>s?1-s/5:1/(1+s/5);var o=util.fakeGesture(this,t),r=getPointer(o.center,e.frame),a=this._pointerToDate(e,i,r);this.zoom(n,a)}t.preventDefault()},Range.prototype._onTouch=function(t){touchParams.start=this.start,touchParams.end=this.end,touchParams.ignore=!1,touchParams.center=null;var e=ItemSet.itemFromTarget(t);e&&e.selected&&this.options.editable&&(touchParams.ignore=!0)},Range.prototype._onHold=function(){touchParams.ignore=!0},Range.prototype._onPinch=function(t,e,i){if(touchParams.ignore=!0,t.gesture.touches.length>1){touchParams.center||(touchParams.center=getPointer(t.gesture.center,e.frame));var s=1/t.gesture.scale,n=this._pointerToDate(e,i,touchParams.center),o=getPointer(t.gesture.center,e.frame),r=(this._pointerToDate(e,i,o),parseInt(n+(touchParams.start-n)*s)),a=parseInt(n+(touchParams.end-n)*s);this.setRange(r,a)}},Range.prototype._pointerToDate=function(t,e,i){var s;if("horizontal"==e){var n=t.width;return s=this.conversion(n),i.x/s.scale+s.offset}var o=t.height;return s=this.conversion(o),i.y/s.scale+s.offset},Range.prototype.zoom=function(t,e){null==e&&(e=(this.start+this.end)/2);var i=e+(this.start-e)*t,s=e+(this.end-e)*t;this.setRange(i,s)},Range.prototype.move=function(t){var e=this.end-this.start,i=this.start+e*t,s=this.end+e*t;this.start=i,this.end=s},Range.prototype.moveTo=function(t){var e=(this.start+this.end)/2,i=e-t,s=this.start-i,n=this.end-i;this.setRange(s,n)},Emitter(Controller.prototype),Controller.prototype.add=function(t){if(void 0==t.id)throw new Error("Component has no field id");if(!(t instanceof Component||t instanceof Controller))throw new TypeError("Component must be an instance of prototype Component or Controller");t.setController(this),this.components[t.id]=t},Controller.prototype.remove=function(t){var e;for(e in this.components)if(this.components.hasOwnProperty(e)&&(e==t||this.components[e]===t))break;e&&(this.components[e].setController(null),delete this.components[e])},Controller.prototype.repaint=function t(){function t(s,n){n in i||(s.depends&&s.depends.forEach(function(e){t(e,e.id)}),s.parent&&t(s.parent,s.parent.id),e=s.repaint()||e,i[n]=!0)}var e=!1;this.repaintTimer&&(clearTimeout(this.repaintTimer),this.repaintTimer=void 0);var i={};util.forEach(this.components,t),this.emit("repaint"),e&&this.reflow()},Controller.prototype.reflow=function e(){function e(s,n){n in i||(s.depends&&s.depends.forEach(function(t){e(t,t.id)}),s.parent&&e(s.parent,s.parent.id),t=s.reflow()||t,i[n]=!0)}var t=!1;this.reflowTimer&&(clearTimeout(this.reflowTimer),this.reflowTimer=void 0);var i={};util.forEach(this.components,e),this.emit("reflow"),t&&this.repaint()},Component.prototype.setOptions=function(t){t&&(util.extend(this.options,t),this.controller&&(this.requestRepaint(),this.requestReflow()))},Component.prototype.getOption=function(t){var e;return this.options&&(e=this.options[t]),void 0===e&&this.defaultOptions&&(e=this.defaultOptions[t]),e},Component.prototype.setController=function(t){this.controller=t||null},Component.prototype.getController=function(){return this.controller},Component.prototype.getContainer=function(){return null},Component.prototype.getFrame=function(){return this.frame},Component.prototype.repaint=function(){return!1},Component.prototype.reflow=function(){return!1},Component.prototype.hide=function(){return this.frame&&this.frame.parentNode?(this.frame.parentNode.removeChild(this.frame),!0):!1},Component.prototype.show=function(){return this.frame&&this.frame.parentNode?!1:this.repaint()},Component.prototype.requestRepaint=function(){if(!this.controller)throw new Error("Cannot request a repaint: no controller configured");this.controller.emit("request-repaint")},Component.prototype.requestReflow=function(){if(!this.controller)throw new Error("Cannot request a reflow: no controller configured");this.controller.emit("request-reflow")},Panel.prototype=new Component,Panel.prototype.setOptions=Component.prototype.setOptions,Panel.prototype.getContainer=function(){return this.frame},Panel.prototype.repaint=function(){var t=0,e=util.updateProperty,i=util.option.asSize,s=this.options,n=this.frame;if(!n){n=document.createElement("div"),n.className="vpanel";var o=s.className;o&&("function"==typeof o?util.addClassName(n,String(o())):util.addClassName(n,String(o))),this.frame=n,t+=1}if(!n.parentNode){if(!this.parent)throw new Error("Cannot repaint panel: no parent attached");var r=this.parent.getContainer();if(!r)throw new Error("Cannot repaint panel: parent has no container element");r.appendChild(n),t+=1}return t+=e(n.style,"top",i(s.top,"0px")),t+=e(n.style,"left",i(s.left,"0px")),t+=e(n.style,"width",i(s.width,"100%")),t+=e(n.style,"height",i(s.height,"100%")),t>0},Panel.prototype.reflow=function(){var t=0,e=util.updateProperty,i=this.frame;return i?(t+=e(this,"top",i.offsetTop),t+=e(this,"left",i.offsetLeft),t+=e(this,"width",i.offsetWidth),t+=e(this,"height",i.offsetHeight)):t+=1,t>0},RootPanel.prototype=new Panel,RootPanel.prototype.setOptions=Component.prototype.setOptions,RootPanel.prototype.repaint=function(){var t=0,e=util.updateProperty,i=util.option.asSize,s=this.options,n=this.frame;if(n||(n=document.createElement("div"),this.frame=n,this._registerListeners(),t+=1),!n.parentNode){if(!this.container)throw new Error("Cannot repaint root panel: no container attached");this.container.appendChild(n),t+=1}n.className="vis timeline rootpanel "+s.orientation+(s.editable?" editable":"");var o=s.className;return o&&util.addClassName(n,util.option.asString(o)),t+=e(n.style,"top",i(s.top,"0px")),t+=e(n.style,"left",i(s.left,"0px")),t+=e(n.style,"width",i(s.width,"100%")),t+=e(n.style,"height",i(s.height,"100%")),this._updateWatch(),t>0},RootPanel.prototype.reflow=function(){var t=0,e=util.updateProperty,i=this.frame;return i?(t+=e(this,"top",i.offsetTop),t+=e(this,"left",i.offsetLeft),t+=e(this,"width",i.offsetWidth),t+=e(this,"height",i.offsetHeight)):t+=1,t>0},RootPanel.prototype._updateWatch=function(){var t=this.getOption("autoResize");t?this._watch():this._unwatch()},RootPanel.prototype._watch=function(){var t=this;this._unwatch();var e=function(){var e=t.getOption("autoResize");return e?void(t.frame&&(t.frame.clientWidth!=t.width||t.frame.clientHeight!=t.height)&&t.requestReflow()):void t._unwatch()};util.addEventListener(window,"resize",e),this.watchTimer=setInterval(e,1e3)},RootPanel.prototype._unwatch=function(){this.watchTimer&&(clearInterval(this.watchTimer),this.watchTimer=void 0)},RootPanel.prototype.setController=function(t){this.controller=t||null,this.controller?this._registerListeners():this._unregisterListeners()},RootPanel.prototype._registerListeners=function(){if(this.frame&&this.controller&&!this.hammer){this.hammer=Hammer(this.frame,{prevent_default:!0});for(var t in this.listeners)this.listeners.hasOwnProperty(t)&&this.hammer.on(t,this.listeners[t])}},RootPanel.prototype._unregisterListeners=function(){if(this.hammer){for(var t in this.listeners)this.listeners.hasOwnProperty(t)&&this.hammer.off(t,this.listeners[t]);this.hammer=null}},TimeAxis.prototype=new Component,TimeAxis.prototype.setOptions=Component.prototype.setOptions,TimeAxis.prototype.setRange=function(t){if(!(t instanceof Range||t&&t.start&&t.end))throw new TypeError("Range must be an instance of Range, or an object containing start and end.");this.range=t},TimeAxis.prototype.toTime=function(t){var e=this.conversion;return new Date(t/e.scale+e.offset)},TimeAxis.prototype.toScreen=function(t){var e=this.conversion;return(t.valueOf()-e.offset)*e.scale},TimeAxis.prototype.repaint=function(){var t=0,e=util.updateProperty,i=util.option.asSize,s=this.options,n=this.getOption("orientation"),o=this.props,r=this.step,a=this.frame;if(a||(a=document.createElement("div"),this.frame=a,t+=1),a.className="axis",!a.parentNode){if(!this.parent)throw new Error("Cannot repaint time axis: no parent attached");var h=this.parent.getContainer();if(!h)throw new Error("Cannot repaint time axis: parent has no container element");h.appendChild(a),t+=1}var d=a.parentNode;if(d){var c=a.nextSibling;d.removeChild(a);var l="bottom"==n&&this.props.parentHeight&&this.height?this.props.parentHeight-this.height+"px":"0px";if(t+=e(a.style,"top",i(s.top,l)),t+=e(a.style,"left",i(s.left,"0px")),t+=e(a.style,"width",i(s.width,"100%")),t+=e(a.style,"height",i(s.height,this.height+"px")),this._repaintMeasureChars(),this.step){this._repaintStart(),r.first();for(var u=void 0,p=0;r.hasNext()&&1e3>p;){p++;var g=r.getCurrent(),f=this.toScreen(g),m=r.isMajor();this.getOption("showMinorLabels")&&this._repaintMinorText(f,r.getLabelMinor()),m&&this.getOption("showMajorLabels")?(f>0&&(void 0==u&&(u=f),this._repaintMajorText(f,r.getLabelMajor())),this._repaintMajorLine(f)):this._repaintMinorLine(f),r.next()}if(this.getOption("showMajorLabels")){var v=this.toTime(0),y=r.getLabelMajor(v),_=y.length*(o.majorCharWidth||10)+10;(void 0==u||u>_)&&this._repaintMajorText(0,y)}this._repaintEnd()}this._repaintLine(),c?d.insertBefore(a,c):d.appendChild(a)}return t>0},TimeAxis.prototype._repaintStart=function(){var t=this.dom,e=t.redundant;e.majorLines=t.majorLines,e.majorTexts=t.majorTexts,e.minorLines=t.minorLines,e.minorTexts=t.minorTexts,t.majorLines=[],t.majorTexts=[],t.minorLines=[],t.minorTexts=[]},TimeAxis.prototype._repaintEnd=function(){util.forEach(this.dom.redundant,function(t){for(;t.length;){var e=t.pop();e&&e.parentNode&&e.parentNode.removeChild(e)}})},TimeAxis.prototype._repaintMinorText=function(t,e){var i=this.dom.redundant.minorTexts.shift();if(!i){var s=document.createTextNode("");i=document.createElement("div"),i.appendChild(s),i.className="text minor",this.frame.appendChild(i)}this.dom.minorTexts.push(i),i.childNodes[0].nodeValue=e,i.style.left=t+"px",i.style.top=this.props.minorLabelTop+"px"},TimeAxis.prototype._repaintMajorText=function(t,e){var i=this.dom.redundant.majorTexts.shift();if(!i){var s=document.createTextNode(e);i=document.createElement("div"),i.className="text major",i.appendChild(s),this.frame.appendChild(i) +}this.dom.majorTexts.push(i),i.childNodes[0].nodeValue=e,i.style.top=this.props.majorLabelTop+"px",i.style.left=t+"px"},TimeAxis.prototype._repaintMinorLine=function(t){var e=this.dom.redundant.minorLines.shift();e||(e=document.createElement("div"),e.className="grid vertical minor",this.frame.appendChild(e)),this.dom.minorLines.push(e);var i=this.props;e.style.top=i.minorLineTop+"px",e.style.height=i.minorLineHeight+"px",e.style.left=t-i.minorLineWidth/2+"px"},TimeAxis.prototype._repaintMajorLine=function(t){var e=this.dom.redundant.majorLines.shift();e||(e=document.createElement("DIV"),e.className="grid vertical major",this.frame.appendChild(e)),this.dom.majorLines.push(e);var i=this.props;e.style.top=i.majorLineTop+"px",e.style.left=t-i.majorLineWidth/2+"px",e.style.height=i.majorLineHeight+"px"},TimeAxis.prototype._repaintLine=function(){{var t=this.dom.line,e=this.frame;this.options}this.getOption("showMinorLabels")||this.getOption("showMajorLabels")?(t?(e.removeChild(t),e.appendChild(t)):(t=document.createElement("div"),t.className="grid horizontal major",e.appendChild(t),this.dom.line=t),t.style.top=this.props.lineTop+"px"):t&&t.parentElement&&(e.removeChild(t.line),delete this.dom.line)},TimeAxis.prototype._repaintMeasureChars=function(){var t,e=this.dom;if(!e.measureCharMinor){t=document.createTextNode("0");var i=document.createElement("DIV");i.className="text minor measure",i.appendChild(t),this.frame.appendChild(i),e.measureCharMinor=i}if(!e.measureCharMajor){t=document.createTextNode("0");var s=document.createElement("DIV");s.className="text major measure",s.appendChild(t),this.frame.appendChild(s),e.measureCharMajor=s}},TimeAxis.prototype.reflow=function(){var t=0,e=util.updateProperty,i=this.frame,s=this.range;if(!s)throw new Error("Cannot repaint time axis: no range configured");if(i){t+=e(this,"top",i.offsetTop),t+=e(this,"left",i.offsetLeft);var n=this.props,o=this.getOption("showMinorLabels"),r=this.getOption("showMajorLabels"),a=this.dom.measureCharMinor,h=this.dom.measureCharMajor;a&&(n.minorCharHeight=a.clientHeight,n.minorCharWidth=a.clientWidth),h&&(n.majorCharHeight=h.clientHeight,n.majorCharWidth=h.clientWidth);var d=i.parentNode?i.parentNode.offsetHeight:0;switch(d!=n.parentHeight&&(n.parentHeight=d,t+=1),this.getOption("orientation")){case"bottom":n.minorLabelHeight=o?n.minorCharHeight:0,n.majorLabelHeight=r?n.majorCharHeight:0,n.minorLabelTop=0,n.majorLabelTop=n.minorLabelTop+n.minorLabelHeight,n.minorLineTop=-this.top,n.minorLineHeight=Math.max(this.top+n.majorLabelHeight,0),n.minorLineWidth=1,n.majorLineTop=-this.top,n.majorLineHeight=Math.max(this.top+n.minorLabelHeight+n.majorLabelHeight,0),n.majorLineWidth=1,n.lineTop=0;break;case"top":n.minorLabelHeight=o?n.minorCharHeight:0,n.majorLabelHeight=r?n.majorCharHeight:0,n.majorLabelTop=0,n.minorLabelTop=n.majorLabelTop+n.majorLabelHeight,n.minorLineTop=n.minorLabelTop,n.minorLineHeight=Math.max(d-n.majorLabelHeight-this.top),n.minorLineWidth=1,n.majorLineTop=0,n.majorLineHeight=Math.max(d-this.top),n.majorLineWidth=1,n.lineTop=n.majorLabelHeight+n.minorLabelHeight;break;default:throw new Error('Unkown orientation "'+this.getOption("orientation")+'"')}var c=n.minorLabelHeight+n.majorLabelHeight;t+=e(this,"width",i.offsetWidth),t+=e(this,"height",c),this._updateConversion();var l=util.convert(s.start,"Number"),u=util.convert(s.end,"Number"),p=this.toTime(5*(n.minorCharWidth||10)).valueOf()-this.toTime(0).valueOf();this.step=new TimeStep(new Date(l),new Date(u),p),t+=e(n.range,"start",l),t+=e(n.range,"end",u),t+=e(n.range,"minimumStep",p.valueOf())}return t>0},TimeAxis.prototype._updateConversion=function(){var t=this.range;if(!t)throw new Error("No range configured");this.conversion=t.conversion?t.conversion(this.width):Range.conversion(t.start,t.end,this.width)},TimeAxis.prototype.snap=function(t){return this.step.snap(t)},CurrentTime.prototype=new Component,CurrentTime.prototype.setOptions=Component.prototype.setOptions,CurrentTime.prototype.getContainer=function(){return this.frame},CurrentTime.prototype.repaint=function(){var t=this.frame,e=this.parent,i=e.parent.getContainer();if(!e)throw new Error("Cannot repaint bar: no parent attached");if(!i)throw new Error("Cannot repaint bar: parent has no container element");if(!this.getOption("showCurrentTime"))return t&&(i.removeChild(t),delete this.frame),!1;t||(t=document.createElement("div"),t.className="currenttime",t.style.position="absolute",t.style.top="0px",t.style.height="100%",i.appendChild(t),this.frame=t),e.conversion||e._updateConversion();var s=new Date,n=e.toScreen(s);t.style.left=n+"px",t.title="Current time: "+s,void 0!==this.currentTimeTimer&&(clearTimeout(this.currentTimeTimer),delete this.currentTimeTimer);var o=this,r=1/e.conversion.scale/2;return 30>r&&(r=30),this.currentTimeTimer=setTimeout(function(){o.repaint()},r),!1},CustomTime.prototype=new Component,Emitter(CustomTime.prototype),CustomTime.prototype.setOptions=Component.prototype.setOptions,CustomTime.prototype.getContainer=function(){return this.frame},CustomTime.prototype.repaint=function(){var t=this.frame,e=this.parent;if(!e)throw new Error("Cannot repaint bar: no parent attached");var i=e.parent.getContainer();if(!i)throw new Error("Cannot repaint bar: parent has no container element");if(!this.getOption("showCustomTime"))return t&&(i.removeChild(t),delete this.frame),!1;if(!t){t=document.createElement("div"),t.className="customtime",t.style.position="absolute",t.style.top="0px",t.style.height="100%",i.appendChild(t);var s=document.createElement("div");s.style.position="relative",s.style.top="0px",s.style.left="-10px",s.style.height="100%",s.style.width="20px",t.appendChild(s),this.frame=t,this.hammer=Hammer(t,{prevent_default:!0}),this.hammer.on("dragstart",this._onDragStart.bind(this)),this.hammer.on("drag",this._onDrag.bind(this)),this.hammer.on("dragend",this._onDragEnd.bind(this))}e.conversion||e._updateConversion();var n=e.toScreen(this.customTime);return t.style.left=n+"px",t.title="Time: "+this.customTime,!1},CustomTime.prototype.setCustomTime=function(t){this.customTime=new Date(t.valueOf()),this.repaint()},CustomTime.prototype.getCustomTime=function(){return new Date(this.customTime.valueOf())},CustomTime.prototype._onDragStart=function(t){this.eventParams.customTime=this.customTime,t.stopPropagation(),t.preventDefault()},CustomTime.prototype._onDrag=function(t){var e=t.gesture.deltaX,i=this.parent.toScreen(this.eventParams.customTime)+e,s=this.parent.toTime(i);this.setCustomTime(s),this.controller&&this.controller.emit("timechange",{time:this.customTime}),t.stopPropagation(),t.preventDefault()},CustomTime.prototype._onDragEnd=function(t){this.controller&&this.controller.emit("timechanged",{time:this.customTime}),t.stopPropagation(),t.preventDefault()},ItemSet.prototype=new Panel,ItemSet.types={box:ItemBox,range:ItemRange,rangeoverflow:ItemRangeOverflow,point:ItemPoint},ItemSet.prototype.setOptions=Component.prototype.setOptions,ItemSet.prototype.setController=function(t){var e;if(this.controller)for(e in this.eventListeners)this.eventListeners.hasOwnProperty(e)&&this.controller.off(e,this.eventListeners[e]);if(this.controller=t||null,this.controller)for(e in this.eventListeners)this.eventListeners.hasOwnProperty(e)&&this.controller.on(e,this.eventListeners[e])},function(t){var e=null;Object.defineProperty(t,"controller",{get:function(){return e},set:function(){}})}(this),ItemSet.prototype.setRange=function(t){if(!(t instanceof Range||t&&t.start&&t.end))throw new TypeError("Range must be an instance of Range, or an object containing start and end.");this.range=t},ItemSet.prototype.setSelection=function(t){var e,i,s,n;if(t){if(!Array.isArray(t))throw new TypeError("Array expected");for(e=0,i=this.selection.length;i>e;e++)s=this.selection[e],n=this.items[s],n&&n.unselect();for(this.selection=[],e=0,i=t.length;i>e;e++)s=t[e],n=this.items[s],n&&(this.selection.push(s),n.select());this.controller&&this.requestRepaint()}},ItemSet.prototype.getSelection=function(){return this.selection.concat([])},ItemSet.prototype._deselect=function(t){for(var e=this.selection,i=0,s=e.length;s>i;i++)if(e[i]==t){e.splice(i,1);break}},ItemSet.prototype.repaint=function(){var t=0,e=util.updateProperty,i=util.option.asSize,s=this.options,n=this.getOption("orientation"),o=this.defaultOptions,r=this.frame;if(!r){r=document.createElement("div"),r.className="itemset",r["timeline-itemset"]=this;var a=s.className;a&&util.addClassName(r,util.option.asString(a));var h=document.createElement("div");h.className="background",r.appendChild(h),this.dom.background=h;var d=document.createElement("div");d.className="foreground",r.appendChild(d),this.dom.foreground=d;var c=document.createElement("div");c.className="itemset-axis",this.dom.axis=c,this.frame=r,t+=1}if(!this.parent)throw new Error("Cannot repaint itemset: no parent attached");var l=this.parent.getContainer();if(!l)throw new Error("Cannot repaint itemset: parent has no container element");r.parentNode||(l.appendChild(r),t+=1),this.dom.axis.parentNode||(l.appendChild(this.dom.axis),t+=1),t+=e(r.style,"left",i(s.left,"0px")),t+=e(r.style,"top",i(s.top,"0px")),t+=e(r.style,"width",i(s.width,"100%")),t+=e(r.style,"height",i(s.height,this.height+"px")),t+=e(this.dom.axis.style,"left",i(s.left,"0px")),t+=e(this.dom.axis.style,"width",i(s.width,"100%")),t+="bottom"==n?e(this.dom.axis.style,"top",this.height+this.top+"px"):e(this.dom.axis.style,"top",this.top+"px"),this._updateConversion();var u=this,p=this.queue,g=this.itemsData,f=this.items,m={};for(var v in p)if(p.hasOwnProperty(v)){var y=p[v],_=f[v],b=y.action;switch(b){case"add":case"update":var w=g&&g.get(v,m);if(w){var S=w.type||w.start&&w.end&&"range"||s.type||"box",x=ItemSet.types[S];if(_&&(x&&_ instanceof x?(_.data=w,t++):(t+=_.hide(),_=null)),!_){if(!x)throw new TypeError('Unknown item type "'+S+'"');_=new x(u,w,s,o),_.id=y.id,t++}_.repaint(),f[v]=_}delete p[v];break;case"remove":_&&(_.selected&&u._deselect(v),t+=_.hide()),delete f[v],delete p[v];break;default:console.log('Error: unknown action "'+b+'"')}}return util.forEach(this.items,function(e){e.visible?(t+=e.show(),e.reposition()):t+=e.hide()}),t>0},ItemSet.prototype.getForeground=function(){return this.dom.foreground},ItemSet.prototype.getBackground=function(){return this.dom.background},ItemSet.prototype.getAxis=function(){return this.dom.axis},ItemSet.prototype.reflow=function(){var t=0,e=this.options,i=e.margin&&"axis"in e.margin?e.margin.axis:this.defaultOptions.margin.axis,s=e.margin&&"item"in e.margin?e.margin.item:this.defaultOptions.margin.item,n=util.updateProperty,o=util.option.asNumber,r=util.option.asSize,a=this.frame;if(a){this._updateConversion(),util.forEach(this.items,function(e){t+=e.reflow()}),this.stack.update();var h,d=o(e.maxHeight),c=null!=r(e.height);if(c)h=a.offsetHeight;else{var l=this.stack.ordered;if(l.length){var u=l[0].top,p=l[0].top+l[0].height;util.forEach(l,function(t){u=Math.min(u,t.top),p=Math.max(p,t.top+t.height)}),h=p-u+i+s}else h=i+s}null!=d&&(h=Math.min(h,d)),t+=n(this,"height",h),t+=n(this,"top",a.offsetTop),t+=n(this,"left",a.offsetLeft),t+=n(this,"width",a.offsetWidth)}else t+=1;return t>0},ItemSet.prototype.hide=function(){var t=!1;return this.frame&&this.frame.parentNode&&(this.frame.parentNode.removeChild(this.frame),t=!0),this.dom.axis&&this.dom.axis.parentNode&&(this.dom.axis.parentNode.removeChild(this.dom.axis),t=!0),t},ItemSet.prototype.setItems=function(t){var e,i=this,s=this.itemsData;if(t){if(!(t instanceof DataSet||t instanceof DataView))throw new TypeError("Data must be an instance of DataSet");this.itemsData=t}else this.itemsData=null;if(s&&(util.forEach(this.listeners,function(t,e){s.unsubscribe(e,t)}),e=s.getIds(),this._onRemove(e)),this.itemsData){var n=this.id;util.forEach(this.listeners,function(t,e){i.itemsData.on(e,t,n)}),e=this.itemsData.getIds(),this._onAdd(e)}},ItemSet.prototype.getItems=function(){return this.itemsData},ItemSet.prototype.removeItem=function(t){var e=this.itemsData.get(t),i=this._myDataSet();e&&this.options.onRemove(e,function(t){t&&i.remove(t)})},ItemSet.prototype._onUpdate=function(t){this._toQueue("update",t)},ItemSet.prototype._onAdd=function(t){this._toQueue("add",t)},ItemSet.prototype._onRemove=function(t){this._toQueue("remove",t)},ItemSet.prototype._toQueue=function(t,e){var i=this.queue;e.forEach(function(e){i[e]={id:e,action:t}}),this.controller&&this.requestRepaint()},ItemSet.prototype._updateConversion=function(){var t=this.range;if(!t)throw new Error("No range configured");this.conversion=t.conversion?t.conversion(this.width):Range.conversion(t.start,t.end,this.width)},ItemSet.prototype.toTime=function(t){var e=this.conversion;return new Date(t/e.scale+e.offset)},ItemSet.prototype.toScreen=function(t){var e=this.conversion;return(t.valueOf()-e.offset)*e.scale},ItemSet.prototype._onDragStart=function(t){if(this.options.editable){var e=ItemSet.itemFromTarget(t),i=this;if(e&&e.selected){var s=t.target.dragLeftItem,n=t.target.dragRightItem;this.touchParams.itemProps=s?[{item:s,start:e.data.start.valueOf()}]:n?[{item:n,end:e.data.end.valueOf()}]:this.getSelection().map(function(t){var e=i.items[t],s={item:e};return"start"in e.data&&(s.start=e.data.start.valueOf()),"end"in e.data&&(s.end=e.data.end.valueOf()),s}),t.stopPropagation()}}},ItemSet.prototype._onDrag=function(t){if(this.touchParams.itemProps){var e=this.options.snap||null,i=t.gesture.deltaX,s=i/this.conversion.scale;this.touchParams.itemProps.forEach(function(t){if("start"in t){var i=new Date(t.start+s);t.item.data.start=e?e(i):i}if("end"in t){var n=new Date(t.end+s);t.item.data.end=e?e(n):n}}),this.requestReflow(),t.stopPropagation()}},ItemSet.prototype._onDragEnd=function(t){if(this.touchParams.itemProps){var e=[],i=this,s=this._myDataSet();this.touchParams.itemProps.forEach(function(t){var n=t.item.id,o=i.itemsData.get(n),r=!1;"start"in t.item.data&&(r=t.start!=t.item.data.start.valueOf(),o.start=util.convert(t.item.data.start,s.convert.start)),"end"in t.item.data&&(r=r||t.end!=t.item.data.end.valueOf(),o.end=util.convert(t.item.data.end,s.convert.end)),r&&i.options.onMove(o,function(s){s?e.push(s):("start"in t&&(t.item.data.start=t.start),"end"in t&&(t.item.data.end=t.end),i.requestReflow())})}),this.touchParams.itemProps=null,e.length&&s.update(e),t.stopPropagation()}},ItemSet.itemFromTarget=function(t){for(var e=t.target;e;){if(e.hasOwnProperty("timeline-item"))return e["timeline-item"];e=e.parentNode}return null},ItemSet.itemSetFromTarget=function(t){for(var e=t.target;e;){if(e.hasOwnProperty("timeline-itemset"))return e["timeline-itemset"];e=e.parentNode}return null},ItemSet.prototype._myDataSet=function(){for(var t=this.itemsData;t instanceof DataView;)t=t.data;return t},Item.prototype.select=function(){this.selected=!0,this.visible&&this.repaint()},Item.prototype.unselect=function(){this.selected=!1,this.visible&&this.repaint()},Item.prototype.show=function(){return!1},Item.prototype.hide=function(){return!1},Item.prototype.repaint=function(){return!1},Item.prototype.reflow=function(){return!1},Item.prototype.setOffset=function(t){this.offset=t},Item.prototype._repaintDeleteButton=function(t){if(this.selected&&this.options.editable&&!this.dom.deleteButton){var e=this.parent,i=this.id,s=document.createElement("div");s.className="delete",s.title="Delete this item",Hammer(s,{preventDefault:!0}).on("tap",function(t){e.removeItem(i),t.stopPropagation()}),t.appendChild(s),this.dom.deleteButton=s}else!this.selected&&this.dom.deleteButton&&(this.dom.deleteButton.parentNode&&this.dom.deleteButton.parentNode.removeChild(this.dom.deleteButton),this.dom.deleteButton=null)},ItemBox.prototype=new Item(null,null),ItemBox.prototype.repaint=function(){var t=!1,e=this.dom;if(e||(this._create(),e=this.dom,t=!0),e){if(!this.parent)throw new Error("Cannot repaint item: no parent attached");if(!e.box.parentNode){var i=this.parent.getForeground();if(!i)throw new Error("Cannot repaint time axis: parent has no foreground container element");i.appendChild(e.box),t=!0}if(!e.line.parentNode){var s=this.parent.getBackground();if(!s)throw new Error("Cannot repaint time axis: parent has no background container element");s.appendChild(e.line),t=!0}if(!e.dot.parentNode){var n=this.parent.getAxis();if(!s)throw new Error("Cannot repaint time axis: parent has no axis container element");n.appendChild(e.dot),t=!0}if(this._repaintDeleteButton(e.box),this.data.content!=this.content){if(this.content=this.data.content,this.content instanceof Element)e.content.innerHTML="",e.content.appendChild(this.content);else{if(void 0==this.data.content)throw new Error('Property "content" missing in item '+this.data.id);e.content.innerHTML=this.content}t=!0}var o=(this.data.className?" "+this.data.className:"")+(this.selected?" selected":"");this.className!=o&&(this.className=o,e.box.className="item box"+o,e.line.className="item line"+o,e.dot.className="item dot"+o,t=!0)}return t},ItemBox.prototype.show=function(){return this.dom&&this.dom.box.parentNode?!1:this.repaint()},ItemBox.prototype.hide=function(){var t=!1,e=this.dom;return e&&(e.box.parentNode&&(e.box.parentNode.removeChild(e.box),t=!0),e.line.parentNode&&e.line.parentNode.removeChild(e.line),e.dot.parentNode&&e.dot.parentNode.removeChild(e.dot)),t},ItemBox.prototype.reflow=function(){var t,e,i,s,n,o,r,a,h,d,c,l,u=0;if(void 0==this.data.start)throw new Error('Property "start" missing in item '+this.data.id);if(c=this.data,l=this.parent&&this.parent.range,c&&l){var p=l.end-l.start;this.visible=c.start>l.start-p&&c.start0},ItemBox.prototype._create=function(){var t=this.dom;t||(this.dom=t={},t.box=document.createElement("DIV"),t.content=document.createElement("DIV"),t.content.className="content",t.box.appendChild(t.content),t.line=document.createElement("DIV"),t.line.className="line",t.dot=document.createElement("DIV"),t.dot.className="dot",t.box["timeline-item"]=this)},ItemBox.prototype.reposition=function(){var t=this.dom,e=this.props,i=this.options.orientation||this.defaultOptions.orientation;if(t){var s=t.box,n=t.line,o=t.dot;s.style.left=this.left+"px",s.style.top=this.top+"px",n.style.left=e.line.left+"px","top"==i?(n.style.top="0px",n.style.height=this.top+"px"):(n.style.top=this.top+this.height+"px",n.style.height=Math.max(this.parent.height-this.top-this.height+this.props.dot.height/2,0)+"px"),o.style.left=e.dot.left+"px",o.style.top=e.dot.top+"px"}},ItemPoint.prototype=new Item(null,null),ItemPoint.prototype.repaint=function(){var t=!1,e=this.dom;if(e||(this._create(),e=this.dom,t=!0),e){if(!this.parent)throw new Error("Cannot repaint item: no parent attached");var i=this.parent.getForeground();if(!i)throw new Error("Cannot repaint time axis: parent has no foreground container element");if(e.point.parentNode||(i.appendChild(e.point),i.appendChild(e.point),t=!0),this.data.content!=this.content){if(this.content=this.data.content,this.content instanceof Element)e.content.innerHTML="",e.content.appendChild(this.content);else{if(void 0==this.data.content)throw new Error('Property "content" missing in item '+this.data.id);e.content.innerHTML=this.content}t=!0}this._repaintDeleteButton(e.point);var s=(this.data.className?" "+this.data.className:"")+(this.selected?" selected":"");this.className!=s&&(this.className=s,e.point.className="item point"+s,t=!0)}return t},ItemPoint.prototype.show=function(){return this.dom&&this.dom.point.parentNode?!1:this.repaint()},ItemPoint.prototype.hide=function(){var t=!1,e=this.dom;return e&&e.point.parentNode&&(e.point.parentNode.removeChild(e.point),t=!0),t},ItemPoint.prototype.reflow=function(){var t,e,i,s,n,o,r,a,h,d,c=0;if(void 0==this.data.start)throw new Error('Property "start" missing in item '+this.data.id);if(h=this.data,d=this.parent&&this.parent.range,h&&d){var l=d.end-d.start;this.visible=h.start>d.start-l&&h.start0},ItemPoint.prototype._create=function(){var t=this.dom;t||(this.dom=t={},t.point=document.createElement("div"),t.content=document.createElement("div"),t.content.className="content",t.point.appendChild(t.content),t.dot=document.createElement("div"),t.dot.className="dot",t.point.appendChild(t.dot),t.point["timeline-item"]=this)},ItemPoint.prototype.reposition=function(){var t=this.dom,e=this.props;t&&(t.point.style.top=this.top+"px",t.point.style.left=this.left+"px",t.content.style.marginLeft=e.content.marginLeft+"px",t.dot.style.top=e.dot.top+"px")},ItemRange.prototype=new Item(null,null),ItemRange.prototype.repaint=function(){var t=!1,e=this.dom;if(e||(this._create(),e=this.dom,t=!0),e){if(!this.parent)throw new Error("Cannot repaint item: no parent attached");var i=this.parent.getForeground();if(!i)throw new Error("Cannot repaint time axis: parent has no foreground container element");if(e.box.parentNode||(i.appendChild(e.box),t=!0),this.data.content!=this.content){if(this.content=this.data.content,this.content instanceof Element)e.content.innerHTML="",e.content.appendChild(this.content);else{if(void 0==this.data.content)throw new Error('Property "content" missing in item '+this.data.id);e.content.innerHTML=this.content}t=!0}this._repaintDeleteButton(e.box),this._repaintDragLeft(),this._repaintDragRight();var s=(this.data.className?" "+this.data.className:"")+(this.selected?" selected":"");this.className!=s&&(this.className=s,e.box.className="item range"+s,t=!0)}return t},ItemRange.prototype.show=function(){return this.dom&&this.dom.box.parentNode?!1:this.repaint()},ItemRange.prototype.hide=function(){var t=!1,e=this.dom;return e&&e.box.parentNode&&(e.box.parentNode.removeChild(e.box),t=!0),t},ItemRange.prototype.reflow=function(){var t,e,i,s,n,o,r,a,h,d,c,l,u,p,g,f,m=0;if(void 0==this.data.start)throw new Error('Property "start" missing in item '+this.data.id);if(void 0==this.data.end)throw new Error('Property "end" missing in item '+this.data.id);return h=this.data,d=this.parent&&this.parent.range,this.visible=h&&d?h.startd.start:!1,this.visible&&(t=this.dom,t?(e=this.props,i=this.options,o=this.parent,r=o.toScreen(this.data.start)+this.offset,a=o.toScreen(this.data.end)+this.offset,c=util.updateProperty,l=t.box,u=o.width,g=i.orientation||this.defaultOptions.orientation,s=i.margin&&i.margin.axis||this.defaultOptions.margin.axis,n=i.padding||this.defaultOptions.padding,m+=c(e.content,"width",t.content.offsetWidth),m+=c(this,"height",l.offsetHeight),-u>r&&(r=-u),a>2*u&&(a=2*u),p=0>r?Math.min(-r,a-r-e.content.width-2*n):0,m+=c(e.content,"left",p),"top"==g?(f=s,m+=c(this,"top",f)):(f=o.height-this.height-s,m+=c(this,"top",f)),m+=c(this,"left",r),m+=c(this,"width",Math.max(a-r,1))):m+=1),m>0},ItemRange.prototype._create=function(){var t=this.dom;t||(this.dom=t={},t.box=document.createElement("div"),t.content=document.createElement("div"),t.content.className="content",t.box.appendChild(t.content),t.box["timeline-item"]=this)},ItemRange.prototype.reposition=function(){var t=this.dom,e=this.props;t&&(t.box.style.top=this.top+"px",t.box.style.left=this.left+"px",t.box.style.width=this.width+"px",t.content.style.left=e.content.left+"px")},ItemRange.prototype._repaintDragLeft=function(){if(this.selected&&this.options.editable&&!this.dom.dragLeft){var t=document.createElement("div");t.className="drag-left",t.dragLeftItem=this,Hammer(t,{preventDefault:!0}).on("drag",function(){}),this.dom.box.appendChild(t),this.dom.dragLeft=t}else!this.selected&&this.dom.dragLeft&&(this.dom.dragLeft.parentNode&&this.dom.dragLeft.parentNode.removeChild(this.dom.dragLeft),this.dom.dragLeft=null)},ItemRange.prototype._repaintDragRight=function(){if(this.selected&&this.options.editable&&!this.dom.dragRight){var t=document.createElement("div");t.className="drag-right",t.dragRightItem=this,Hammer(t,{preventDefault:!0}).on("drag",function(){}),this.dom.box.appendChild(t),this.dom.dragRight=t}else!this.selected&&this.dom.dragRight&&(this.dom.dragRight.parentNode&&this.dom.dragRight.parentNode.removeChild(this.dom.dragRight),this.dom.dragRight=null)},ItemRangeOverflow.prototype=new ItemRange(null,null),ItemRangeOverflow.prototype.repaint=function(){var t=!1,e=this.dom;if(e||(this._create(),e=this.dom,t=!0),e){if(!this.parent)throw new Error("Cannot repaint item: no parent attached");var i=this.parent.getForeground();if(!i)throw new Error("Cannot repaint time axis: parent has no foreground container element");if(e.box.parentNode||(i.appendChild(e.box),t=!0),this.data.content!=this.content){if(this.content=this.data.content,this.content instanceof Element)e.content.innerHTML="",e.content.appendChild(this.content);else{if(void 0==this.data.content)throw new Error('Property "content" missing in item '+this.id);e.content.innerHTML=this.content}t=!0}this._repaintDeleteButton(e.box),this._repaintDragLeft(),this._repaintDragRight();var s=(this.data.className?" "+this.data.className:"")+(this.selected?" selected":"");this.className!=s&&(this.className=s,e.box.className="item rangeoverflow"+s,t=!0)}return t},ItemRangeOverflow.prototype.reposition=function(){var t=this.dom,e=this.props;t&&(t.box.style.top=this.top+"px",t.box.style.left=this.left+"px",t.box.style.width=this._width+"px",t.content.style.left=e.content.left+"px")},Group.prototype=new Component,Group.prototype.setOptions=Component.prototype.setOptions,Group.prototype.getContainer=function(){return this.parent.getContainer()},Group.prototype.setItems=function(t){if(this.itemset&&(this.itemset.hide(),this.itemset.setItems(),this.parent.controller.remove(this.itemset),this.itemset=null),t){var e=this.groupId,i=Object.create(this.options);this.itemset=new ItemSet(this,null,i),this.itemset.setRange(this.parent.range),this.view=new DataView(t,{filter:function(t){return t.group==e}}),this.itemset.setItems(this.view),this.parent.controller.add(this.itemset)}},Group.prototype.setSelection=function(t){this.itemset&&this.itemset.setSelection(t)},Group.prototype.getSelection=function(){return this.itemset?this.itemset.getSelection():[]},Group.prototype.repaint=function(){return!1},Group.prototype.reflow=function(){var t=0,e=util.updateProperty;if(t+=e(this,"top",this.itemset?this.itemset.top:0),t+=e(this,"height",this.itemset?this.itemset.height:0),this.label){var i=this.label.firstChild;t+=e(this.props.label,"width",i.clientWidth),t+=e(this.props.label,"height",i.clientHeight)}else t+=e(this.props.label,"width",0),t+=e(this.props.label,"height",0);return t>0},GroupSet.prototype=new Panel,GroupSet.prototype.setOptions=Component.prototype.setOptions,GroupSet.prototype.setRange=function(){},GroupSet.prototype.setItems=function(t){this.itemsData=t;for(var e in this.groups)if(this.groups.hasOwnProperty(e)){var i=this.groups[e];i.setItems(t)}},GroupSet.prototype.getItems=function(){return this.itemsData},GroupSet.prototype.setRange=function(t){this.range=t},GroupSet.prototype.setGroups=function(t){var e,i=this;if(this.groupsData&&(util.forEach(this.listeners,function(t,e){i.groupsData.unsubscribe(e,t)}),e=this.groupsData.getIds(),this._onRemove(e)),t?t instanceof DataSet?this.groupsData=t:(this.groupsData=new DataSet({convert:{start:"Date",end:"Date"}}),this.groupsData.add(t)):this.groupsData=null,this.groupsData){var s=this.id;util.forEach(this.listeners,function(t,e){i.groupsData.on(e,t,s)}),e=this.groupsData.getIds(),this._onAdd(e)}},GroupSet.prototype.getGroups=function(){return this.groupsData},GroupSet.prototype.setSelection=function(t){var e=[],i=this.groups;for(var s in i)if(i.hasOwnProperty(s)){var n=i[s];n.setSelection(t)}return e},GroupSet.prototype.getSelection=function(){var t=[],e=this.groups;for(var i in e)if(e.hasOwnProperty(i)){var s=e[i];t=t.concat(s.getSelection())}return t},GroupSet.prototype.repaint=function(){var t,e,i,s,n=0,o=util.updateProperty,r=util.option.asSize,a=util.option.asElement,h=this.options,d=this.dom.frame,c=this.dom.labels,l=this.dom.labelSet;if(!this.parent)throw new Error("Cannot repaint groupset: no parent attached");var u=this.parent.getContainer();if(!u)throw new Error("Cannot repaint groupset: parent has no container element");if(!d){d=document.createElement("div"),d.className="groupset",d["timeline-groupset"]=this,this.dom.frame=d;var p=h.className;p&&util.addClassName(d,util.option.asString(p)),n+=1}d.parentNode||(u.appendChild(d),n+=1);var g=a(h.labelContainer);if(!g)throw new Error('Cannot repaint groupset: option "labelContainer" not defined');c||(c=document.createElement("div"),c.className="labels",this.dom.labels=c),l||(l=document.createElement("div"),l.className="label-set",c.appendChild(l),this.dom.labelSet=l),c.parentNode&&c.parentNode==g||(c.parentNode&&c.parentNode.removeChild(c.parentNode),g.appendChild(c)),n+=o(d.style,"height",r(h.height,this.height+"px")),n+=o(d.style,"top",r(h.top,"0px")),n+=o(d.style,"left",r(h.left,"0px")),n+=o(d.style,"width",r(h.width,"100%")),n+=o(l.style,"top",r(h.top,"0px")),n+=o(l.style,"height",r(h.height,this.height+"px"));var f=this,m=this.queue,v=this.groups,y=this.groupsData,_=Object.keys(m);if(_.length){_.forEach(function(t){var e=m[t],i=v[t];switch(e){case"add":case"update":if(!i){var s=Object.create(f.options);util.extend(s,{height:null,maxHeight:null}),i=new Group(f,t,s),i.setItems(f.itemsData),v[t]=i,f.controller.add(i)}i.data=y.get(t),delete m[t];break;case"remove":i&&(i.setItems(),delete v[t],f.controller.remove(i)),delete m[t];break;default:console.log('Error: unknown action "'+e+'"')}});var b=this.groupsData.getIds({order:this.options.groupOrder});for(t=0;t0},GroupSet.prototype._createLabel=function(t){var e=this.groups[t],i=document.createElement("div");i.className="vlabel";var s=document.createElement("div");s.className="inner",i.appendChild(s);var n=e.data&&e.data.content;n instanceof Element?s.appendChild(n):void 0!=n&&(s.innerHTML=n);var o=e.data&&e.data.className;return o&&util.addClassName(i,o),e.label=i,i},GroupSet.prototype.getContainer=function(){return this.dom.frame},GroupSet.prototype.getLabelsWidth=function(){return this.props.labels.width},GroupSet.prototype.reflow=function(){var t,e,i=0,s=this.options,n=util.updateProperty,o=util.option.asNumber,r=util.option.asSize,a=this.dom.frame; +if(a){var h,d=o(s.maxHeight),c=null!=r(s.height);if(c)h=a.offsetHeight;else{h=0;for(t in this.groups)this.groups.hasOwnProperty(t)&&(e=this.groups[t],h+=e.height)}null!=d&&(h=Math.min(h,d)),i+=n(this,"height",h),i+=n(this,"top",a.offsetTop),i+=n(this,"left",a.offsetLeft),i+=n(this,"width",a.offsetWidth)}var l=0;for(t in this.groups)if(this.groups.hasOwnProperty(t)){e=this.groups[t];var u=e.props&&e.props.label&&e.props.label.width||0;l=Math.max(l,u)}return i+=n(this.props.labels,"width",l),i>0},GroupSet.prototype.hide=function(){return this.dom.frame&&this.dom.frame.parentNode?(this.dom.frame.parentNode.removeChild(this.dom.frame),!0):!1},GroupSet.prototype.show=function(){return this.dom.frame&&this.dom.frame.parentNode?!1:this.repaint()},GroupSet.prototype._onUpdate=function(t){this._toQueue(t,"update")},GroupSet.prototype._onAdd=function(t){this._toQueue(t,"add")},GroupSet.prototype._onRemove=function(t){this._toQueue(t,"remove")},GroupSet.prototype._toQueue=function(t,e){var i=this.queue;t.forEach(function(t){i[t]=e}),this.controller&&this.requestRepaint()},GroupSet.groupFromTarget=function(t){for(var e,i=t.target;i;){if(i.hasOwnProperty("timeline-groupset")){e=i["timeline-groupset"];break}i=i.parentNode}if(e)for(var s in e.groups)if(e.groups.hasOwnProperty(s)){var n=e.groups[s];if(n.itemset&&ItemSet.itemSetFromTarget(t)==n.itemset)return n}return null},Timeline.prototype.on=function(t,e){this.controller.on(t,e)},Timeline.prototype.off=function(t,e){this.controller.off(t,e)},Timeline.prototype.setOptions=function(t){util.extend(this.options,t),this.range.setRange(t.start,t.end),("editable"in t||"selectable"in t)&&this.setSelection(this.options.selectable?this.getSelection():[]);var e=function(t){if(!(this.options[t]instanceof Function)||2!=this.options[t].length)throw new Error("option "+t+" must be a function "+t+"(item, callback)")}.bind(this);["onAdd","onUpdate","onRemove","onMove"].forEach(e),this.controller.reflow(),this.controller.repaint()},Timeline.prototype.setCustomTime=function(t){if(!this.customtime)throw new Error("Cannot get custom time: Custom time bar is not enabled");this.customtime.setCustomTime(t)},Timeline.prototype.getCustomTime=function(){if(!this.customtime)throw new Error("Cannot get custom time: Custom time bar is not enabled");return this.customtime.getCustomTime()},Timeline.prototype.setItems=function(t){var e,i=null==this.itemsData;if(t?t instanceof DataSet&&(e=t):e=null,t instanceof DataSet||(e=new DataSet({convert:{start:"Date",end:"Date"}}),e.add(t)),this.itemsData=e,this.content.setItems(e),i&&(void 0==this.options.start||void 0==this.options.end)){var s=this.getItemRange(),n=s.min,o=s.max;if(null!=n&&null!=o){var r=o.valueOf()-n.valueOf();0>=r&&(r=864e5),n=new Date(n.valueOf()-.05*r),o=new Date(o.valueOf()+.05*r)}void 0!=this.options.start&&(n=util.convert(this.options.start,"Date")),void 0!=this.options.end&&(o=util.convert(this.options.end,"Date")),(null!=n||null!=o)&&this.range.setRange(n,o)}},Timeline.prototype.setGroups=function(t){var e=this;this.groupsData=t;var i=this.groupsData?GroupSet:ItemSet;if(!(this.content instanceof i)){this.content&&(this.content.hide(),this.content.setItems&&this.content.setItems(),this.content.setGroups&&this.content.setGroups(),this.controller.remove(this.content));var s=Object.create(this.options);util.extend(s,{top:function(){return"top"==e.options.orientation?e.timeaxis.height:e.itemPanel.height-e.timeaxis.height-e.content.height},left:null,width:"100%",height:function(){return e.options.height?e.itemPanel.height-e.timeaxis.height:null},maxHeight:function(){if(e.options.maxHeight){if(!util.isNumber(e.options.maxHeight))throw new TypeError("Number expected for property maxHeight");return e.options.maxHeight-e.timeaxis.height}return null},labelContainer:function(){return e.labelPanel.getContainer()}}),this.content=new i(this.itemPanel,[this.timeaxis],s),this.content.setRange&&this.content.setRange(this.range),this.content.setItems&&this.content.setItems(this.itemsData),this.content.setGroups&&this.content.setGroups(this.groupsData),this.controller.add(this.content)}},Timeline.prototype.getItemRange=function(){var t=this.itemsData,e=null,i=null;if(t){var s=t.min("start");e=s?s.start.valueOf():null;var n=t.max("start");n&&(i=n.start.valueOf());var o=t.max("end");o&&(i=null==i?o.end.valueOf():Math.max(i,o.end.valueOf()))}return{min:null!=e?new Date(e):null,max:null!=i?new Date(i):null}},Timeline.prototype.setSelection=function(t){this.content&&this.content.setSelection(t)},Timeline.prototype.getSelection=function(){return this.content?this.content.getSelection():[]},Timeline.prototype.setWindow=function(t,e){this.range.setRange(t,e)},Timeline.prototype.getWindow=function(){var t=this.range.getRange();return{start:new Date(t.start),end:new Date(t.end)}},Timeline.prototype._onSelectItem=function(t){if(this.options.selectable){var e=t.gesture.srcEvent&&t.gesture.srcEvent.ctrlKey,i=t.gesture.srcEvent&&t.gesture.srcEvent.shiftKey;if(e||i)return void this._onMultiSelectItem(t);var s=ItemSet.itemFromTarget(t),n=s?[s.id]:[];this.setSelection(n),this.controller.emit("select",{items:this.getSelection()}),t.stopPropagation()}},Timeline.prototype._onAddItem=function(t){if(this.options.selectable&&this.options.editable){var e=this,i=ItemSet.itemFromTarget(t);if(i){var s=e.itemsData.get(i.id);this.options.onUpdate(s,function(t){t&&e.itemsData.update(t)})}else{var n=vis.util.getAbsoluteLeft(this.rootPanel.frame),o=t.gesture.center.pageX-n,r={start:this.timeaxis.snap(this._toTime(o)),content:"new item"},a=util.randomUUID();r[this.itemsData.fieldId]=a;var h=GroupSet.groupFromTarget(t);h&&(r.group=h.groupId),this.options.onAdd(r,function(t){t&&(e.itemsData.add(r),e.controller.once("repaint",function(){e.setSelection([a]),e.controller.emit("select",{items:e.getSelection()})}.bind(e)))})}}},Timeline.prototype._onMultiSelectItem=function(t){if(this.options.selectable){var e,i=ItemSet.itemFromTarget(t);if(i){e=this.getSelection();var s=e.indexOf(i.id);-1==s?e.push(i.id):e.splice(s,1),this.setSelection(e),this.controller.emit("select",{items:this.getSelection()}),t.stopPropagation()}}},Timeline.prototype._toTime=function(t){var e=this.range.conversion(this.content.width);return new Date(t/e.scale+e.offset)},Timeline.prototype._toScreen=function(t){var e=this.range.conversion(this.content.width);return(t.valueOf()-e.offset)*e.scale},function(t){function e(t){return D=t,u()}function i(){C=0,M=D.charAt(0)}function s(){C++,M=D.charAt(C)}function n(){return D.charAt(C+1)}function o(t){return O.test(t)}function r(t,e){if(t||(t={}),e)for(var i in e)e.hasOwnProperty(i)&&(t[i]=e[i]);return t}function a(t,e,i){for(var s=e.split("."),n=t;s.length;){var o=s.shift();s.length?(n[o]||(n[o]={}),n=n[o]):n[o]=i}}function h(t,e){for(var i,s,n=null,o=[t],a=t;a.parent;)o.push(a.parent),a=a.parent;if(a.nodes)for(i=0,s=a.nodes.length;s>i;i++)if(e.id===a.nodes[i].id){n=a.nodes[i];break}for(n||(n={id:e.id},t.node&&(n.attr=r(n.attr,t.node))),i=o.length-1;i>=0;i--){var h=o[i];h.nodes||(h.nodes=[]),-1==h.nodes.indexOf(n)&&h.nodes.push(n)}e.attr&&(n.attr=r(n.attr,e.attr))}function d(t,e){if(t.edges||(t.edges=[]),t.edges.push(e),t.edge){var i=r({},t.edge);e.attr=r(i,e.attr)}}function c(t,e,i,s,n){var o={from:e,to:i,type:s};return t.edge&&(o.attr=r({},t.edge)),o.attr=r(o.attr||{},n),o}function l(){for(N=E.NULL,I="";" "==M||" "==M||"\n"==M||"\r"==M;)s();do{var t=!1;if("#"==M){for(var e=C-1;" "==D.charAt(e)||" "==D.charAt(e);)e--;if("\n"==D.charAt(e)||""==D.charAt(e)){for(;""!=M&&"\n"!=M;)s();t=!0}}if("/"==M&&"/"==n()){for(;""!=M&&"\n"!=M;)s();t=!0}if("/"==M&&"*"==n()){for(;""!=M;){if("*"==M&&"/"==n()){s(),s();break}s()}t=!0}for(;" "==M||" "==M||"\n"==M||"\r"==M;)s()}while(t);if(""==M)return void(N=E.DELIMITER);var i=M+n();if(T[i])return N=E.DELIMITER,I=i,s(),void s();if(T[M])return N=E.DELIMITER,I=M,void s();if(o(M)||"-"==M){for(I+=M,s();o(M);)I+=M,s();return"false"==I?I=!1:"true"==I?I=!0:isNaN(Number(I))||(I=Number(I)),void(N=E.IDENTIFIER)}if('"'==M){for(s();""!=M&&('"'!=M||'"'==M&&'"'==n());)I+=M,'"'==M&&s(),s();if('"'!=M)throw b('End of string " expected');return s(),void(N=E.IDENTIFIER)}for(N=E.UNKNOWN;""!=M;)I+=M,s();throw new SyntaxError('Syntax error in part "'+w(I,30)+'"')}function u(){var t={};if(i(),l(),"strict"==I&&(t.strict=!0,l()),("graph"==I||"digraph"==I)&&(t.type=I,l()),N==E.IDENTIFIER&&(t.id=I,l()),"{"!=I)throw b("Angle bracket { expected");if(l(),p(t),"}"!=I)throw b("Angle bracket } expected");if(l(),""!==I)throw b("End of file expected");return l(),delete t.node,delete t.edge,delete t.graph,t}function p(t){for(;""!==I&&"}"!=I;)g(t),";"==I&&l()}function g(t){var e=f(t);if(e)return void y(t,e);var i=m(t);if(!i){if(N!=E.IDENTIFIER)throw b("Identifier expected");var s=I;if(l(),"="==I){if(l(),N!=E.IDENTIFIER)throw b("Identifier expected");t[s]=I,l()}else v(t,s)}}function f(t){var e=null;if("subgraph"==I&&(e={},e.type="subgraph",l(),N==E.IDENTIFIER&&(e.id=I,l())),"{"==I){if(l(),e||(e={}),e.parent=t,e.node=t.node,e.edge=t.edge,e.graph=t.graph,p(e),"}"!=I)throw b("Angle bracket } expected");l(),delete e.node,delete e.edge,delete e.graph,delete e.parent,t.subgraphs||(t.subgraphs=[]),t.subgraphs.push(e)}return e}function m(t){return"node"==I?(l(),t.node=_(),"node"):"edge"==I?(l(),t.edge=_(),"edge"):"graph"==I?(l(),t.graph=_(),"graph"):null}function v(t,e){var i={id:e},s=_();s&&(i.attr=s),h(t,i),y(t,e)}function y(t,e){for(;"->"==I||"--"==I;){var i,s=I;l();var n=f(t);if(n)i=n;else{if(N!=E.IDENTIFIER)throw b("Identifier or subgraph expected");i=I,h(t,{id:i}),l()}var o=_(),r=c(t,e,i,s,o);d(t,r),e=i}}function _(){for(var t=null;"["==I;){for(l(),t={};""!==I&&"]"!=I;){if(N!=E.IDENTIFIER)throw b("Attribute name expected");var e=I;if(l(),"="!=I)throw b("Equal sign = expected");if(l(),N!=E.IDENTIFIER)throw b("Attribute value expected");var i=I;a(t,e,i),l(),","==I&&l()}if("]"!=I)throw b("Bracket ] expected");l()}return t}function b(t){return new SyntaxError(t+', got "'+w(I,30)+'" (char '+C+")")}function w(t,e){return t.length<=e?t:t.substr(0,27)+"..."}function S(t,e,i){t instanceof Array?t.forEach(function(t){e instanceof Array?e.forEach(function(e){i(t,e)}):i(t,e)}):e instanceof Array?e.forEach(function(e){i(t,e)}):i(t,e)}function x(t){function i(t){var e={from:t.from,to:t.to};return r(e,t.attr),e.style="->"==t.type?"arrow":"line",e}var s=e(t),n={nodes:[],edges:[],options:{}};return s.nodes&&s.nodes.forEach(function(t){var e={id:t.id,label:String(t.label||t.id)};r(e,t.attr),e.image&&(e.shape="image"),n.nodes.push(e)}),s.edges&&s.edges.forEach(function(t){var e,s;e=t.from instanceof Object?t.from.nodes:{id:t.from},s=t.to instanceof Object?t.to.nodes:{id:t.to},t.from instanceof Object&&t.from.edges&&t.from.edges.forEach(function(t){var e=i(t);n.edges.push(e)}),S(e,s,function(e,s){var o=c(n,e.id,s.id,t.type,t.attr),r=i(o);n.edges.push(r)}),t.to instanceof Object&&t.to.edges&&t.to.edges.forEach(function(t){var e=i(t);n.edges.push(e)})}),s.attr&&(n.options=s.attr),n}var E={NULL:0,DELIMITER:1,IDENTIFIER:2,UNKNOWN:3},T={"{":!0,"}":!0,"[":!0,"]":!0,";":!0,"=":!0,",":!0,"->":!0,"--":!0},D="",C=0,M="",I="",N=E.NULL,O=/[a-zA-Z_0-9.:#]/;t.parseDOT=e,t.DOTToGraph=x}("undefined"!=typeof util?util:exports),"undefined"!=typeof CanvasRenderingContext2D&&(CanvasRenderingContext2D.prototype.circle=function(t,e,i){this.beginPath(),this.arc(t,e,i,0,2*Math.PI,!1)},CanvasRenderingContext2D.prototype.square=function(t,e,i){this.beginPath(),this.rect(t-i,e-i,2*i,2*i)},CanvasRenderingContext2D.prototype.triangle=function(t,e,i){this.beginPath();var s=2*i,n=s/2,o=Math.sqrt(3)/6*s,r=Math.sqrt(s*s-n*n);this.moveTo(t,e-(r-o)),this.lineTo(t+n,e+o),this.lineTo(t-n,e+o),this.lineTo(t,e-(r-o)),this.closePath()},CanvasRenderingContext2D.prototype.triangleDown=function(t,e,i){this.beginPath();var s=2*i,n=s/2,o=Math.sqrt(3)/6*s,r=Math.sqrt(s*s-n*n);this.moveTo(t,e+(r-o)),this.lineTo(t+n,e-o),this.lineTo(t-n,e-o),this.lineTo(t,e+(r-o)),this.closePath()},CanvasRenderingContext2D.prototype.star=function(t,e,i){this.beginPath();for(var s=0;10>s;s++){var n=s%2===0?1.3*i:.5*i;this.lineTo(t+n*Math.sin(2*s*Math.PI/10),e-n*Math.cos(2*s*Math.PI/10))}this.closePath()},CanvasRenderingContext2D.prototype.roundRect=function(t,e,i,s,n){var o=Math.PI/180;0>i-2*n&&(n=i/2),0>s-2*n&&(n=s/2),this.beginPath(),this.moveTo(t+n,e),this.lineTo(t+i-n,e),this.arc(t+i-n,e+n,n,270*o,360*o,!1),this.lineTo(t+i,e+s-n),this.arc(t+i-n,e+s-n,n,0,90*o,!1),this.lineTo(t+n,e+s),this.arc(t+n,e+s-n,n,90*o,180*o,!1),this.lineTo(t,e+n),this.arc(t+n,e+n,n,180*o,270*o,!1)},CanvasRenderingContext2D.prototype.ellipse=function(t,e,i,s){var n=.5522848,o=i/2*n,r=s/2*n,a=t+i,h=e+s,d=t+i/2,c=e+s/2;this.beginPath(),this.moveTo(t,c),this.bezierCurveTo(t,c-r,d-o,e,d,e),this.bezierCurveTo(d+o,e,a,c-r,a,c),this.bezierCurveTo(a,c+r,d+o,h,d,h),this.bezierCurveTo(d-o,h,t,c+r,t,c)},CanvasRenderingContext2D.prototype.database=function(t,e,i,s){var n=1/3,o=i,r=s*n,a=.5522848,h=o/2*a,d=r/2*a,c=t+o,l=e+r,u=t+o/2,p=e+r/2,g=e+(s-r/2),f=e+s;this.beginPath(),this.moveTo(c,p),this.bezierCurveTo(c,p+d,u+h,l,u,l),this.bezierCurveTo(u-h,l,t,p+d,t,p),this.bezierCurveTo(t,p-d,u-h,e,u,e),this.bezierCurveTo(u+h,e,c,p-d,c,p),this.lineTo(c,g),this.bezierCurveTo(c,g+d,u+h,f,u,f),this.bezierCurveTo(u-h,f,t,g+d,t,g),this.lineTo(t,p)},CanvasRenderingContext2D.prototype.arrow=function(t,e,i,s){var n=t-s*Math.cos(i),o=e-s*Math.sin(i),r=t-.9*s*Math.cos(i),a=e-.9*s*Math.sin(i),h=n+s/3*Math.cos(i+.5*Math.PI),d=o+s/3*Math.sin(i+.5*Math.PI),c=n+s/3*Math.cos(i-.5*Math.PI),l=o+s/3*Math.sin(i-.5*Math.PI);this.beginPath(),this.moveTo(t,e),this.lineTo(h,d),this.lineTo(r,a),this.lineTo(c,l),this.closePath()},CanvasRenderingContext2D.prototype.dashedLine=function(t,e,i,s,n){n||(n=[10,5]),0==u&&(u=.001);var o=n.length;this.moveTo(t,e);for(var r=i-t,a=s-e,h=a/r,d=Math.sqrt(r*r+a*a),c=0,l=!0;d>=.1;){var u=n[c++%o];u>d&&(u=d);var p=Math.sqrt(u*u/(1+h*h));0>r&&(p=-p),t+=p,e+=h*p,this[l?"lineTo":"moveTo"](t,e),d-=u,l=!l}}),Node.prototype.resetCluster=function(){this.formationScale=void 0,this.clusterSize=1,this.containedNodes={},this.containedEdges={},this.clusterSessions=[]},Node.prototype.attachEdge=function(t){-1==this.edges.indexOf(t)&&this.edges.push(t),-1==this.dynamicEdges.indexOf(t)&&this.dynamicEdges.push(t),this.dynamicEdgesLength=this.dynamicEdges.length},Node.prototype.detachEdge=function(t){var e=this.edges.indexOf(t);-1!=e&&(this.edges.splice(e,1),this.dynamicEdges.splice(e,1)),this.dynamicEdgesLength=this.dynamicEdges.length},Node.prototype.setProperties=function(t,e){if(t){if(this.originalLabel=void 0,void 0!==t.id&&(this.id=t.id),void 0!==t.label&&(this.label=t.label,this.originalLabel=t.label),void 0!==t.title&&(this.title=t.title),void 0!==t.group&&(this.group=t.group),void 0!==t.x&&(this.x=t.x),void 0!==t.y&&(this.y=t.y),void 0!==t.value&&(this.value=t.value),void 0!==t.level&&(this.level=t.level),void 0!==t.mass&&(this.mass=t.mass),void 0!==t.horizontalAlignLeft&&(this.horizontalAlignLeft=t.horizontalAlignLeft),void 0!==t.verticalAlignTop&&(this.verticalAlignTop=t.verticalAlignTop),void 0!==t.triggerFunction&&(this.triggerFunction=t.triggerFunction),void 0===this.id)throw"Node must have an id";if(this.group){var i=this.grouplist.get(this.group);for(var s in i)i.hasOwnProperty(s)&&(this[s]=i[s])}if(void 0!==t.shape&&(this.shape=t.shape),void 0!==t.image&&(this.image=t.image),void 0!==t.radius&&(this.radius=t.radius),void 0!==t.color&&(this.color=Node.parseColor(t.color)),void 0!==t.fontColor&&(this.fontColor=t.fontColor),void 0!==t.fontSize&&(this.fontSize=t.fontSize),void 0!==t.fontFace&&(this.fontFace=t.fontFace),void 0!==this.image&&""!=this.image){if(!this.imagelist)throw"No imagelist provided";this.imageObj=this.imagelist.load(this.image)}switch(this.xFixed=this.xFixed||void 0!==t.x&&!t.allowedToMoveX,this.yFixed=this.yFixed||void 0!==t.y&&!t.allowedToMoveY,this.radiusFixed=this.radiusFixed||void 0!==t.radius,"image"==this.shape&&(this.radiusMin=e.nodes.widthMin,this.radiusMax=e.nodes.widthMax),this.shape){case"database":this.draw=this._drawDatabase,this.resize=this._resizeDatabase;break;case"box":this.draw=this._drawBox,this.resize=this._resizeBox;break;case"circle":this.draw=this._drawCircle,this.resize=this._resizeCircle;break;case"ellipse":this.draw=this._drawEllipse,this.resize=this._resizeEllipse;break;case"image":this.draw=this._drawImage,this.resize=this._resizeImage;break;case"text":this.draw=this._drawText,this.resize=this._resizeText;break;case"dot":this.draw=this._drawDot,this.resize=this._resizeShape;break;case"square":this.draw=this._drawSquare,this.resize=this._resizeShape;break;case"triangle":this.draw=this._drawTriangle,this.resize=this._resizeShape;break;case"triangleDown":this.draw=this._drawTriangleDown,this.resize=this._resizeShape;break;case"star":this.draw=this._drawStar,this.resize=this._resizeShape;break;default:this.draw=this._drawEllipse,this.resize=this._resizeEllipse}this._reset()}},Node.parseColor=function(t){var e;if(util.isString(t))if(util.isValidHex(t)){var i=util.hexToHSV(t),s={h:i.h,s:.45*i.s,v:Math.min(1,1.05*i.v)},n={h:i.h,s:Math.min(1,1.25*i.v),v:.6*i.v},o=util.HSVToHex(n.h,n.h,n.v),r=util.HSVToHex(s.h,s.s,s.v);e={background:t,border:o,highlight:{background:r,border:o}}}else e={background:t,border:t,highlight:{background:t,border:t}};else e={},e.background=t.background||"white",e.border=t.border||e.background,util.isString(t.highlight)?e.highlight={border:t.highlight,background:t.highlight}:(e.highlight={},e.highlight.background=t.highlight&&t.highlight.background||e.background,e.highlight.border=t.highlight&&t.highlight.border||e.border);return e},Node.prototype.select=function(){this.selected=!0,this._reset()},Node.prototype.unselect=function(){this.selected=!1,this._reset()},Node.prototype.clearSizeCache=function(){this._reset()},Node.prototype._reset=function(){this.width=void 0,this.height=void 0},Node.prototype.getTitle=function(){return this.title},Node.prototype.distanceToBorder=function(t,e){var i=1;switch(this.width||this.resize(t),this.shape){case"circle":case"dot":return this.radius+i;case"ellipse":var s=this.width/2,n=this.height/2,o=Math.sin(e)*s,r=Math.cos(e)*n;return s*n/Math.sqrt(o*o+r*r);case"box":case"image":case"text":default:return this.width?Math.min(Math.abs(this.width/2/Math.cos(e)),Math.abs(this.height/2/Math.sin(e)))+i:0}},Node.prototype._setForce=function(t,e){this.fx=t,this.fy=e},Node.prototype._addForce=function(t,e){this.fx+=t,this.fy+=e},Node.prototype.discreteStep=function(t){if(!this.xFixed){var e=this.damping*this.vx,i=(this.fx-e)/this.mass;this.vx+=i*t,this.x+=this.vx*t}if(!this.yFixed){var s=this.damping*this.vy,n=(this.fy-s)/this.mass;this.vy+=n*t,this.y+=this.vy*t}},Node.prototype.discreteStepLimited=function(t,e){if(this.xFixed)this.fx=0;else{var i=this.damping*this.vx,s=(this.fx-i)/this.mass;this.vx+=s*t,this.vx=Math.abs(this.vx)>e?this.vx>0?e:-e:this.vx,this.x+=this.vx*t}if(this.yFixed)this.fy=0;else{var n=this.damping*this.vy,o=(this.fy-n)/this.mass;this.vy+=o*t,this.vy=Math.abs(this.vy)>e?this.vy>0?e:-e:this.vy,this.y+=this.vy*t}},Node.prototype.isFixed=function(){return this.xFixed&&this.yFixed},Node.prototype.isMoving=function(t){return Math.abs(this.vx)>t||Math.abs(this.vy)>t},Node.prototype.isSelected=function(){return this.selected},Node.prototype.getValue=function(){return this.value},Node.prototype.getDistance=function(t,e){var i=this.x-t,s=this.y-e;return Math.sqrt(i*i+s*s)},Node.prototype.setValueRange=function(t,e){if(!this.radiusFixed&&void 0!==this.value)if(e==t)this.radius=(this.radiusMin+this.radiusMax)/2;else{var i=(this.radiusMax-this.radiusMin)/(e-t);this.radius=(this.value-t)*i+this.radiusMin}this.baseRadiusValue=this.radius},Node.prototype.draw=function(){throw"Draw method not initialized for node"},Node.prototype.resize=function(){throw"Resize method not initialized for node"},Node.prototype.isOverlappingWith=function(t){return this.leftt.left&&this.topt.top},Node.prototype._resizeImage=function(){if(!this.width||!this.height){var t,e;if(this.value){this.radius=this.baseRadiusValue;var i=this.imageObj.height/this.imageObj.width;void 0!==i?(t=this.radius||this.imageObj.width,e=this.radius*i||this.imageObj.height):(t=0,e=0)}else t=this.imageObj.width,e=this.imageObj.height;this.width=t,this.height=e,this.growthIndicator=0,this.width>0&&this.height>0&&(this.width+=Math.min(this.clusterSize-1,this.maxNodeSizeIncrements)*this.clusterSizeWidthFactor,this.height+=Math.min(this.clusterSize-1,this.maxNodeSizeIncrements)*this.clusterSizeHeightFactor,this.radius+=Math.min(this.clusterSize-1,this.maxNodeSizeIncrements)*this.clusterSizeRadiusFactor,this.growthIndicator=this.width-t)}},Node.prototype._drawImage=function(t){this._resizeImage(t),this.left=this.x-this.width/2,this.top=this.y-this.height/2;var e;if(0!=this.imageObj.width){if(this.clusterSize>1){var i=this.clusterSize>1?10:0;i*=this.graphScaleInv,i=Math.min(.2*this.width,i),t.globalAlpha=.5,t.drawImage(this.imageObj,this.left-i,this.top-i,this.width+2*i,this.height+2*i)}t.globalAlpha=1,t.drawImage(this.imageObj,this.left,this.top,this.width,this.height),e=this.y+this.height/2}else e=this.y;this._label(t,this.label,this.x,e,void 0,"top")},Node.prototype._resizeBox=function(t){if(!this.width){var e=5,i=this.getTextSize(t);this.width=i.width+2*e,this.height=i.height+2*e,this.width+=.5*Math.min(this.clusterSize-1,this.maxNodeSizeIncrements)*this.clusterSizeWidthFactor,this.height+=.5*Math.min(this.clusterSize-1,this.maxNodeSizeIncrements)*this.clusterSizeHeightFactor,this.growthIndicator=this.width-(i.width+2*e)}},Node.prototype._drawBox=function(t){this._resizeBox(t),this.left=this.x-this.width/2,this.top=this.y-this.height/2;var e=2.5,i=2;t.strokeStyle=this.selected?this.color.highlight.border:this.color.border,this.clusterSize>1&&(t.lineWidth=(this.selected?i:1)+(this.clusterSize>1?e:0),t.lineWidth*=this.graphScaleInv,t.lineWidth=Math.min(.1*this.width,t.lineWidth),t.roundRect(this.left-2*t.lineWidth,this.top-2*t.lineWidth,this.width+4*t.lineWidth,this.height+4*t.lineWidth,this.radius),t.stroke()),t.lineWidth=(this.selected?i:1)+(this.clusterSize>1?e:0),t.lineWidth*=this.graphScaleInv,t.lineWidth=Math.min(.1*this.width,t.lineWidth),t.fillStyle=this.selected?this.color.highlight.background:this.color.background,t.roundRect(this.left,this.top,this.width,this.height,this.radius),t.fill(),t.stroke(),this._label(t,this.label,this.x,this.y)},Node.prototype._resizeDatabase=function(t){if(!this.width){var e=5,i=this.getTextSize(t),s=i.width+2*e;this.width=s,this.height=s,this.width+=Math.min(this.clusterSize-1,this.maxNodeSizeIncrements)*this.clusterSizeWidthFactor,this.height+=Math.min(this.clusterSize-1,this.maxNodeSizeIncrements)*this.clusterSizeHeightFactor,this.radius+=Math.min(this.clusterSize-1,this.maxNodeSizeIncrements)*this.clusterSizeRadiusFactor,this.growthIndicator=this.width-s}},Node.prototype._drawDatabase=function(t){this._resizeDatabase(t),this.left=this.x-this.width/2,this.top=this.y-this.height/2;var e=2.5,i=2;t.strokeStyle=this.selected?this.color.highlight.border:this.color.border,this.clusterSize>1&&(t.lineWidth=(this.selected?i:1)+(this.clusterSize>1?e:0),t.lineWidth*=this.graphScaleInv,t.lineWidth=Math.min(.1*this.width,t.lineWidth),t.database(this.x-this.width/2-2*t.lineWidth,this.y-.5*this.height-2*t.lineWidth,this.width+4*t.lineWidth,this.height+4*t.lineWidth),t.stroke()),t.lineWidth=(this.selected?i:1)+(this.clusterSize>1?e:0),t.lineWidth*=this.graphScaleInv,t.lineWidth=Math.min(.1*this.width,t.lineWidth),t.fillStyle=this.selected?this.color.highlight.background:this.color.background,t.database(this.x-this.width/2,this.y-.5*this.height,this.width,this.height),t.fill(),t.stroke(),this._label(t,this.label,this.x,this.y)},Node.prototype._resizeCircle=function(t){if(!this.width){var e=5,i=this.getTextSize(t),s=Math.max(i.width,i.height)+2*e;this.radius=s/2,this.width=s,this.height=s,this.radius+=.5*Math.min(this.clusterSize-1,this.maxNodeSizeIncrements)*this.clusterSizeRadiusFactor,this.growthIndicator=this.radius-.5*s}},Node.prototype._drawCircle=function(t){this._resizeCircle(t),this.left=this.x-this.width/2,this.top=this.y-this.height/2;var e=2.5,i=2;t.strokeStyle=this.selected?this.color.highlight.border:this.color.border,this.clusterSize>1&&(t.lineWidth=(this.selected?i:1)+(this.clusterSize>1?e:0),t.lineWidth*=this.graphScaleInv,t.lineWidth=Math.min(.1*this.width,t.lineWidth),t.circle(this.x,this.y,this.radius+2*t.lineWidth),t.stroke()),t.lineWidth=(this.selected?i:1)+(this.clusterSize>1?e:0),t.lineWidth*=this.graphScaleInv,t.lineWidth=Math.min(.1*this.width,t.lineWidth),t.fillStyle=this.selected?this.color.highlight.background:this.color.background,t.circle(this.x,this.y,this.radius),t.fill(),t.stroke(),this._label(t,this.label,this.x,this.y)},Node.prototype._resizeEllipse=function(t){if(!this.width){var e=this.getTextSize(t);this.width=1.5*e.width,this.height=2*e.height,this.width1&&(t.lineWidth=(this.selected?i:1)+(this.clusterSize>1?e:0),t.lineWidth*=this.graphScaleInv,t.lineWidth=Math.min(.1*this.width,t.lineWidth),t.ellipse(this.left-2*t.lineWidth,this.top-2*t.lineWidth,this.width+4*t.lineWidth,this.height+4*t.lineWidth),t.stroke()),t.lineWidth=(this.selected?i:1)+(this.clusterSize>1?e:0),t.lineWidth*=this.graphScaleInv,t.lineWidth=Math.min(.1*this.width,t.lineWidth),t.fillStyle=this.selected?this.color.highlight.background:this.color.background,t.ellipse(this.left,this.top,this.width,this.height),t.fill(),t.stroke(),this._label(t,this.label,this.x,this.y)},Node.prototype._drawDot=function(t){this._drawShape(t,"circle")},Node.prototype._drawTriangle=function(t){this._drawShape(t,"triangle")},Node.prototype._drawTriangleDown=function(t){this._drawShape(t,"triangleDown")},Node.prototype._drawSquare=function(t){this._drawShape(t,"square")},Node.prototype._drawStar=function(t){this._drawShape(t,"star")},Node.prototype._resizeShape=function(){if(!this.width){this.radius=this.baseRadiusValue;var t=2*this.radius;this.width=t,this.height=t,this.width+=Math.min(this.clusterSize-1,this.maxNodeSizeIncrements)*this.clusterSizeWidthFactor,this.height+=Math.min(this.clusterSize-1,this.maxNodeSizeIncrements)*this.clusterSizeHeightFactor,this.radius+=.5*Math.min(this.clusterSize-1,this.maxNodeSizeIncrements)*this.clusterSizeRadiusFactor,this.growthIndicator=this.width-t}},Node.prototype._drawShape=function(t,e){this._resizeShape(t),this.left=this.x-this.width/2,this.top=this.y-this.height/2;var i=2.5,s=2,n=2;switch(e){case"dot":n=2;break;case"square":n=2;break;case"triangle":n=3;break;case"triangleDown":n=3;break;case"star":n=4}t.strokeStyle=this.selected?this.color.highlight.border:this.color.border,this.clusterSize>1&&(t.lineWidth=(this.selected?s:1)+(this.clusterSize>1?i:0),t.lineWidth*=this.graphScaleInv,t.lineWidth=Math.min(.1*this.width,t.lineWidth),t[e](this.x,this.y,this.radius+n*t.lineWidth),t.stroke()),t.lineWidth=(this.selected?s:1)+(this.clusterSize>1?i:0),t.lineWidth*=this.graphScaleInv,t.lineWidth=Math.min(.1*this.width,t.lineWidth),t.fillStyle=this.selected?this.color.highlight.background:this.color.background,t[e](this.x,this.y,this.radius),t.fill(),t.stroke(),this.label&&this._label(t,this.label,this.x,this.y+this.height/2,void 0,"top")},Node.prototype._resizeText=function(t){if(!this.width){var e=5,i=this.getTextSize(t);this.width=i.width+2*e,this.height=i.height+2*e,this.width+=Math.min(this.clusterSize-1,this.maxNodeSizeIncrements)*this.clusterSizeWidthFactor,this.height+=Math.min(this.clusterSize-1,this.maxNodeSizeIncrements)*this.clusterSizeHeightFactor,this.radius+=Math.min(this.clusterSize-1,this.maxNodeSizeIncrements)*this.clusterSizeRadiusFactor,this.growthIndicator=this.width-(i.width+2*e)}},Node.prototype._drawText=function(t){this._resizeText(t),this.left=this.x-this.width/2,this.top=this.y-this.height/2,this._label(t,this.label,this.x,this.y)},Node.prototype._label=function(t,e,i,s,n,o){if(e&&this.fontSize*this.graphScale>this.fontDrawThreshold){t.font=(this.selected?"bold ":"")+this.fontSize+"px "+this.fontFace,t.fillStyle=this.fontColor||"black",t.textAlign=n||"center",t.textBaseline=o||"middle";for(var r=e.split("\n"),a=r.length,h=this.fontSize+4,d=s+(1-a)/2*h,c=0;a>c;c++)t.fillText(r[c],i,d),d+=h}},Node.prototype.getTextSize=function(t){if(void 0!==this.label){t.font=(this.selected?"bold ":"")+this.fontSize+"px "+this.fontFace;for(var e=this.label.split("\n"),i=(this.fontSize+4)*e.length,s=0,n=0,o=e.length;o>n;n++)s=Math.max(s,t.measureText(e[n]).width);return{width:s,height:i}}return{width:0,height:0}},Node.prototype.inArea=function(){return void 0!==this.width?this.x+this.width*this.graphScaleInv>=this.canvasTopLeft.x&&this.x-this.width*this.graphScaleInv=this.canvasTopLeft.y&&this.y-this.height*this.graphScaleInv=this.canvasTopLeft.x&&this.x=this.canvasTopLeft.y&&this.yh}return!1},Edge.prototype._drawLine=function(t){if(t.strokeStyle=1==this.selected?this.color.highlight:this.color.color,t.lineWidth=this._getLineWidth(),this.from!=this.to){this._line(t);var e;if(this.label){if(1==this.smooth){var i=.5*(.5*(this.from.x+this.via.x)+.5*(this.to.x+this.via.x)),s=.5*(.5*(this.from.y+this.via.y)+.5*(this.to.y+this.via.y));e={x:i,y:s}}else e=this._pointOnLine(.5);this._label(t,this.label,e.x,e.y)}}else{var n,o,r=this.length/4,a=this.from;a.width||a.resize(t),a.width>a.height?(n=a.x+a.width/2,o=a.y-r):(n=a.x+r,o=a.y-a.height/2),this._circle(t,n,o,r),e=this._pointOnCircle(n,o,r,.5),this._label(t,this.label,e.x,e.y)}},Edge.prototype._getLineWidth=function(){return 1==this.selected?Math.min(2*this.width,this.widthMax)*this.graphScaleInv:this.width*this.graphScaleInv},Edge.prototype._line=function(t){t.beginPath(),t.moveTo(this.from.x,this.from.y),1==this.smooth?t.quadraticCurveTo(this.via.x,this.via.y,this.to.x,this.to.y):t.lineTo(this.to.x,this.to.y),t.stroke()},Edge.prototype._circle=function(t,e,i,s){t.beginPath(),t.arc(e,i,s,0,2*Math.PI,!1),t.stroke()},Edge.prototype._label=function(t,e,i,s){if(e){t.font=(this.from.selected||this.to.selected?"bold ":"")+this.fontSize+"px "+this.fontFace,t.fillStyle="white";var n=t.measureText(e).width,o=this.fontSize,r=i-n/2,a=s-o/2;t.fillRect(r,a,n,o),t.fillStyle=this.fontColor||"black",t.textAlign="left",t.textBaseline="top",t.fillText(e,r,a)}},Edge.prototype._drawDashLine=function(t){if(t.strokeStyle=1==this.selected?this.color.highlight:this.color.color,t.lineWidth=this._getLineWidth(),void 0!==t.mozDash||void 0!==t.setLineDash){t.beginPath(),t.moveTo(this.from.x,this.from.y);var e=[0];e=void 0!==this.dash.length&&void 0!==this.dash.gap?[this.dash.length,this.dash.gap]:[5,5],"undefined"!=typeof t.setLineDash?(t.setLineDash(e),t.lineDashOffset=0):(t.mozDash=e,t.mozDashOffset=0),1==this.smooth?t.quadraticCurveTo(this.via.x,this.via.y,this.to.x,this.to.y):t.lineTo(this.to.x,this.to.y),t.stroke(),"undefined"!=typeof t.setLineDash?(t.setLineDash([0]),t.lineDashOffset=0):(t.mozDash=[0],t.mozDashOffset=0)}else t.beginPath(),t.lineCap="round",void 0!==this.dash.altLength?t.dashedLine(this.from.x,this.from.y,this.to.x,this.to.y,[this.dash.length,this.dash.gap,this.dash.altLength,this.dash.gap]):void 0!==this.dash.length&&void 0!==this.dash.gap?t.dashedLine(this.from.x,this.from.y,this.to.x,this.to.y,[this.dash.length,this.dash.gap]):(t.moveTo(this.from.x,this.from.y),t.lineTo(this.to.x,this.to.y)),t.stroke();if(this.label){var i;if(1==this.smooth){var s=.5*(.5*(this.from.x+this.via.x)+.5*(this.to.x+this.via.x)),n=.5*(.5*(this.from.y+this.via.y)+.5*(this.to.y+this.via.y));i={x:s,y:n}}else i=this._pointOnLine(.5);this._label(t,this.label,i.x,i.y)}},Edge.prototype._pointOnLine=function(t){return{x:(1-t)*this.from.x+t*this.to.x,y:(1-t)*this.from.y+t*this.to.y}},Edge.prototype._pointOnCircle=function(t,e,i,s){var n=2*(s-3/8)*Math.PI;return{x:t+i*Math.cos(n),y:e-i*Math.sin(n)}},Edge.prototype._drawArrowCenter=function(t){var e;if(1==this.selected?(t.strokeStyle=this.color.highlight,t.fillStyle=this.color.highlight):(t.strokeStyle=this.color.color,t.fillStyle=this.color.color),t.lineWidth=this._getLineWidth(),this.from!=this.to){this._line(t);var i=Math.atan2(this.to.y-this.from.y,this.to.x-this.from.x),s=10+5*this.width;if(1==this.smooth){var n=.5*(.5*(this.from.x+this.via.x)+.5*(this.to.x+this.via.x)),o=.5*(.5*(this.from.y+this.via.y)+.5*(this.to.y+this.via.y));e={x:n,y:o}}else e=this._pointOnLine(.5);t.arrow(e.x,e.y,i,s),t.fill(),t.stroke(),this.label&&this._label(t,this.label,e.x,e.y)}else{var r,a,h=.25*Math.max(100,this.length),d=this.from;d.width||d.resize(t),d.width>d.height?(r=d.x+.5*d.width,a=d.y-h):(r=d.x+h,a=d.y-.5*d.height),this._circle(t,r,a,h);var i=.2*Math.PI,s=10+5*this.width;e=this._pointOnCircle(r,a,h,.5),t.arrow(e.x,e.y,i,s),t.fill(),t.stroke(),this.label&&(e=this._pointOnCircle(r,a,h,.5),this._label(t,this.label,e.x,e.y))}},Edge.prototype._drawArrow=function(t){1==this.selected?(t.strokeStyle=this.color.highlight,t.fillStyle=this.color.highlight):(t.strokeStyle=this.color.color,t.fillStyle=this.color.color),t.lineWidth=this._getLineWidth();var e,i;if(this.from!=this.to){e=Math.atan2(this.to.y-this.from.y,this.to.x-this.from.x);var s=this.to.x-this.from.x,n=this.to.y-this.from.y,o=Math.sqrt(s*s+n*n),r=this.from.distanceToBorder(t,e+Math.PI),a=(o-r)/o,h=a*this.from.x+(1-a)*this.to.x,d=a*this.from.y+(1-a)*this.to.y;1==this.smooth&&(e=Math.atan2(this.to.y-this.via.y,this.to.x-this.via.x),s=this.to.x-this.via.x,n=this.to.y-this.via.y,o=Math.sqrt(s*s+n*n));var c,l,u=this.to.distanceToBorder(t,e),p=(o-u)/o;if(1==this.smooth?(c=(1-p)*this.via.x+p*this.to.x,l=(1-p)*this.via.y+p*this.to.y):(c=(1-p)*this.from.x+p*this.to.x,l=(1-p)*this.from.y+p*this.to.y),t.beginPath(),t.moveTo(h,d),1==this.smooth?t.quadraticCurveTo(this.via.x,this.via.y,c,l):t.lineTo(c,l),t.stroke(),i=10+5*this.width,t.arrow(c,l,e,i),t.fill(),t.stroke(),this.label){var g;if(1==this.smooth){var f=.5*(.5*(this.from.x+this.via.x)+.5*(this.to.x+this.via.x)),m=.5*(.5*(this.from.y+this.via.y)+.5*(this.to.y+this.via.y));g={x:f,y:m}}else g=this._pointOnLine(.5);this._label(t,this.label,g.x,g.y)}}else{var v,y,_,b=this.from,w=.25*Math.max(100,this.length);b.width||b.resize(t),b.width>b.height?(v=b.x+.5*b.width,y=b.y-w,_={x:v,y:b.y,angle:.9*Math.PI}):(v=b.x+w,y=b.y-.5*b.height,_={x:b.x,y:y,angle:.6*Math.PI}),t.beginPath(),t.arc(v,y,w,0,2*Math.PI,!1),t.stroke(),i=10+5*this.width,t.arrow(_.x,_.y,_.angle,i),t.fill(),t.stroke(),this.label&&(g=this._pointOnCircle(v,y,w,.5),this._label(t,this.label,g.x,g.y))}},Edge.prototype._getDistanceToEdge=function(t,e,i,s,n,o){if(1==this.smooth){var r,a,h,d,c,l,u=1e9;for(r=0;10>r;r++)a=.1*r,h=Math.pow(1-a,2)*t+2*a*(1-a)*this.via.x+Math.pow(a,2)*i,d=Math.pow(1-a,2)*e+2*a*(1-a)*this.via.y+Math.pow(a,2)*s,c=Math.abs(n-h),l=Math.abs(o-d),u=Math.min(u,Math.sqrt(c*c+l*l));return u}var p=i-t,g=s-e,f=p*p+g*g,m=((n-t)*p+(o-e)*g)/f;m>1?m=1:0>m&&(m=0);var h=t+m*p,d=e+m*g,c=h-n,l=d-o;return Math.sqrt(c*c+l*l)},Edge.prototype.setScale=function(t){this.graphScaleInv=1/t},Edge.prototype.select=function(){this.selected=!0},Edge.prototype.unselect=function(){this.selected=!1},Edge.prototype.positionBezierNode=function(){null!==this.via&&(this.via.x=.5*(this.from.x+this.to.x),this.via.y=.5*(this.from.y+this.to.y))},Popup.prototype.setPosition=function(t,e){this.x=parseInt(t),this.y=parseInt(e)},Popup.prototype.setText=function(t){this.frame.innerHTML=t},Popup.prototype.show=function(t){if(void 0===t&&(t=!0),t){var e=this.frame.clientHeight,i=this.frame.clientWidth,s=this.frame.parentNode.clientHeight,n=this.frame.parentNode.clientWidth,o=this.y-e;o+e+this.padding>s&&(o=s-e-this.padding),on&&(r=n-i-this.padding),rthis.constants.clustering.clusterThreshold&&1==this.constants.clustering.enabled&&this.clusterToFit(this.constants.clustering.reduceToNodes,!1),this._calculateForces())},_calculateForces:function(){this._calculateGravitationalForces(),this._calculateNodeForces(),1==this.constants.smoothCurves?this._calculateSpringForcesWithSupport():this._calculateSpringForces()},_updateCalculationNodes:function(){if(1==this.constants.smoothCurves){this.calculationNodes={},this.calculationNodeIndices=[];for(var t in this.nodes)this.nodes.hasOwnProperty(t)&&(this.calculationNodes[t]=this.nodes[t]);var e=this.sectors.support.nodes;for(var i in e)e.hasOwnProperty(i)&&(this.edges.hasOwnProperty(e[i].parentEdgeId)?this.calculationNodes[i]=e[i]:e[i]._setForce(0,0));for(var s in this.calculationNodes)this.calculationNodes.hasOwnProperty(s)&&this.calculationNodeIndices.push(s)}else this.calculationNodes=this.nodes,this.calculationNodeIndices=this.nodeIndices},_calculateGravitationalForces:function(){var t,e,i,s,n,o=this.calculationNodes,r=this.constants.physics.centralGravity,a=0;for(n=0;nSimulation Mode:Barnes HutRepulsionHierarchical
Options:
',this.containerElement.parentElement.insertBefore(this.physicsConfiguration,this.containerElement),this.optionsDiv=document.createElement("div"),this.optionsDiv.style.fontSize="14px",this.optionsDiv.style.fontFamily="verdana",this.containerElement.parentElement.insertBefore(this.optionsDiv,this.containerElement);var e;e=document.getElementById("graph_BH_gc"),e.onchange=showValueOfRange.bind(this,"graph_BH_gc",-1,"physics_barnesHut_gravitationalConstant"),e=document.getElementById("graph_BH_cg"),e.onchange=showValueOfRange.bind(this,"graph_BH_cg",1,"physics_centralGravity"),e=document.getElementById("graph_BH_sc"),e.onchange=showValueOfRange.bind(this,"graph_BH_sc",1,"physics_springConstant"),e=document.getElementById("graph_BH_sl"),e.onchange=showValueOfRange.bind(this,"graph_BH_sl",1,"physics_springLength"),e=document.getElementById("graph_BH_damp"),e.onchange=showValueOfRange.bind(this,"graph_BH_damp",1,"physics_damping"),e=document.getElementById("graph_R_nd"),e.onchange=showValueOfRange.bind(this,"graph_R_nd",1,"physics_repulsion_nodeDistance"),e=document.getElementById("graph_R_cg"),e.onchange=showValueOfRange.bind(this,"graph_R_cg",1,"physics_centralGravity"),e=document.getElementById("graph_R_sc"),e.onchange=showValueOfRange.bind(this,"graph_R_sc",1,"physics_springConstant"),e=document.getElementById("graph_R_sl"),e.onchange=showValueOfRange.bind(this,"graph_R_sl",1,"physics_springLength"),e=document.getElementById("graph_R_damp"),e.onchange=showValueOfRange.bind(this,"graph_R_damp",1,"physics_damping"),e=document.getElementById("graph_H_nd"),e.onchange=showValueOfRange.bind(this,"graph_H_nd",1,"physics_hierarchicalRepulsion_nodeDistance"),e=document.getElementById("graph_H_cg"),e.onchange=showValueOfRange.bind(this,"graph_H_cg",1,"physics_centralGravity"),e=document.getElementById("graph_H_sc"),e.onchange=showValueOfRange.bind(this,"graph_H_sc",1,"physics_springConstant"),e=document.getElementById("graph_H_sl"),e.onchange=showValueOfRange.bind(this,"graph_H_sl",1,"physics_springLength"),e=document.getElementById("graph_H_damp"),e.onchange=showValueOfRange.bind(this,"graph_H_damp",1,"physics_damping"),e=document.getElementById("graph_H_direction"),e.onchange=showValueOfRange.bind(this,"graph_H_direction",t,"hierarchicalLayout_direction"),e=document.getElementById("graph_H_levsep"),e.onchange=showValueOfRange.bind(this,"graph_H_levsep",1,"hierarchicalLayout_levelSeparation"),e=document.getElementById("graph_H_nspac"),e.onchange=showValueOfRange.bind(this,"graph_H_nspac",1,"hierarchicalLayout_nodeSpacing");var i=document.getElementById("graph_physicsMethod1"),s=document.getElementById("graph_physicsMethod2"),n=document.getElementById("graph_physicsMethod3");s.checked=!0,this.constants.physics.barnesHut.enabled&&(i.checked=!0),this.constants.hierarchicalLayout.enabled&&(n.checked=!0);var o=document.getElementById("graph_toggleSmooth"),r=document.getElementById("graph_repositionNodes"),a=document.getElementById("graph_generateOptions");o.onclick=graphToggleSmoothCurves.bind(this),r.onclick=graphRepositionNodes.bind(this),a.onclick=graphGenerateOptions.bind(this),o.style.background=1==this.constants.smoothCurves?"#A4FF56":"#FF8532",switchConfigurations.apply(this),i.onchange=switchConfigurations.bind(this),s.onchange=switchConfigurations.bind(this),n.onchange=switchConfigurations.bind(this)}},_overWriteGraphConstants:function(t,e){var i=t.split("_");1==i.length?this.constants[i[0]]=e:2==i.length?this.constants[i[0]][i[1]]=e:3==i.length&&(this.constants[i[0]][i[1]][i[2]]=e)}},hierarchalRepulsionMixin={_calculateNodeForces:function(){var t,e,i,s,n,o,r,a,h,d,c=this.calculationNodes,l=this.calculationNodeIndices,u=5,p=.5*-u,g=this.constants.physics.hierarchicalRepulsion.nodeDistance,f=g;for(h=0;hi&&(o=m*i+u,0==i?i=.01:o/=i,s=t*o,n=e*o,r.fx-=s,r.fy-=n,a.fx+=s,a.fy+=n)}}},barnesHutMixin={_calculateNodeForces:function(){var t,e=this.calculationNodes,i=this.calculationNodeIndices,s=i.length;this._formBarnesHutTree(e,i);for(var n=this.barnesHutTree,o=0;s>o;o++)t=e[i[o]],this._getForceContribution(n.root.children.NW,t),this._getForceContribution(n.root.children.NE,t),this._getForceContribution(n.root.children.SW,t),this._getForceContribution(n.root.children.SE,t)},_getForceContribution:function(t,e){if(t.childrenCount>0){var i,s,n;if(i=t.centerOfMass.x-e.x,s=t.centerOfMass.y-e.y,n=Math.sqrt(i*i+s*s),n*t.calcSize>this.constants.physics.barnesHut.theta){0==n&&(n=.1*Math.random(),i=n);var o=this.constants.physics.barnesHut.gravitationalConstant*t.mass*e.mass/(n*n*n),r=i*o,a=s*o;e.fx+=r,e.fy+=a}else if(4==t.childrenCount)this._getForceContribution(t.children.NW,e),this._getForceContribution(t.children.NE,e),this._getForceContribution(t.children.SW,e),this._getForceContribution(t.children.SE,e);else if(t.children.data.id!=e.id){0==n&&(n=.5*Math.random(),i=n);var o=this.constants.physics.barnesHut.gravitationalConstant*t.mass*e.mass/(n*n*n),r=i*o,a=s*o;e.fx+=r,e.fy+=a}}},_formBarnesHutTree:function(t,e){for(var i,s=e.length,n=Number.MAX_VALUE,o=Number.MAX_VALUE,r=-Number.MAX_VALUE,a=-Number.MAX_VALUE,h=0;s>h;h++){var d=t[e[h]].x,c=t[e[h]].y;n>d&&(n=d),d>r&&(r=d),o>c&&(o=c),c>a&&(a=c)}var l=Math.abs(r-n)-Math.abs(a-o);l>0?(o-=.5*l,a+=.5*l):(n+=.5*l,r-=.5*l);var u=1e-5,p=Math.max(u,Math.abs(r-n)),g=.5*p,f=.5*(n+r),m=.5*(o+a),v={root:{centerOfMass:{x:0,y:0},mass:0,range:{minX:f-g,maxX:f+g,minY:m-g,maxY:m+g},size:p,calcSize:1/p,children:{data:null},maxWidth:0,level:0,childrenCount:4}};for(this._splitBranch(v.root),h=0;s>h;h++)i=t[e[h]],this._placeInTree(v.root,i);this.barnesHutTree=v},_updateBranchMass:function(t,e){var i=t.mass+e.mass,s=1/i;t.centerOfMass.x=t.centerOfMass.x*t.mass+e.x*e.mass,t.centerOfMass.x*=s,t.centerOfMass.y=t.centerOfMass.y*t.mass+e.y*e.mass,t.centerOfMass.y*=s,t.mass=i;var n=Math.max(Math.max(e.height,e.radius),e.width);t.maxWidth=t.maxWidthe.x?t.children.NW.range.maxY>e.y?this._placeInRegion(t,e,"NW"):this._placeInRegion(t,e,"SW"):t.children.NW.range.maxY>e.y?this._placeInRegion(t,e,"NE"):this._placeInRegion(t,e,"SE")},_placeInRegion:function(t,e,i){switch(t.children[i].childrenCount){case 0:t.children[i].children.data=e,t.children[i].childrenCount=1,this._updateBranchMass(t.children[i],e);break;case 1:t.children[i].children.data.x==e.x&&t.children[i].children.data.y==e.y?(e.x+=Math.random(),e.y+=Math.random()):(this._splitBranch(t.children[i]),this._placeInTree(t.children[i],e));break;case 4:this._placeInTree(t.children[i],e)}},_splitBranch:function(t){var e=null;1==t.childrenCount&&(e=t.children.data,t.mass=0,t.centerOfMass.x=0,t.centerOfMass.y=0),t.childrenCount=4,t.children.data=null,this._insertRegion(t,"NW"),this._insertRegion(t,"NE"),this._insertRegion(t,"SW"),this._insertRegion(t,"SE"),null!=e&&this._placeInTree(t,e)},_insertRegion:function(t,e){var i,s,n,o,r=.5*t.size;switch(e){case"NW":i=t.range.minX,s=t.range.minX+r,n=t.range.minY,o=t.range.minY+r;break;case"NE":i=t.range.minX+r,s=t.range.maxX,n=t.range.minY,o=t.range.minY+r;break;case"SW":i=t.range.minX,s=t.range.minX+r,n=t.range.minY+r,o=t.range.maxY;break;case"SE":i=t.range.minX+r,s=t.range.maxX,n=t.range.minY+r,o=t.range.maxY}t.children[e]={centerOfMass:{x:0,y:0},mass:0,range:{minX:i,maxX:s,minY:n,maxY:o},size:.5*t.size,calcSize:2*t.calcSize,children:{data:null},maxWidth:0,level:t.level+1,childrenCount:0}},_drawTree:function(t,e){void 0!==this.barnesHutTree&&(t.lineWidth=1,this._drawBranch(this.barnesHutTree.root,t,e))},_drawBranch:function(t,e,i){void 0===i&&(i="#FF0000"),4==t.childrenCount&&(this._drawBranch(t.children.NW,e),this._drawBranch(t.children.NE,e),this._drawBranch(t.children.SE,e),this._drawBranch(t.children.SW,e)),e.strokeStyle=i,e.beginPath(),e.moveTo(t.range.minX,t.range.minY),e.lineTo(t.range.maxX,t.range.minY),e.stroke(),e.beginPath(),e.moveTo(t.range.maxX,t.range.minY),e.lineTo(t.range.maxX,t.range.maxY),e.stroke(),e.beginPath(),e.moveTo(t.range.maxX,t.range.maxY),e.lineTo(t.range.minX,t.range.maxY),e.stroke(),e.beginPath(),e.moveTo(t.range.minX,t.range.maxY),e.lineTo(t.range.minX,t.range.minY),e.stroke()}},repulsionMixin={_calculateNodeForces:function(){var t,e,i,s,n,o,r,a,h,d,c,l=this.calculationNodes,u=this.calculationNodeIndices,p=-2/3,g=4/3,f=this.constants.physics.repulsion.nodeDistance,m=f;for(d=0;di&&(r=.5*m>i?1:v*i+g,r*=0==o?1:1+o*this.constants.clustering.forceAmplification,r/=i,s=t*r,n=e*r,a.fx-=s,a.fy-=n,h.fx+=s,h.fy+=n)}}},HierarchicalLayoutMixin={_setupHierarchicalLayout:function(){if(1==this.constants.hierarchicalLayout.enabled){"RL"==this.constants.hierarchicalLayout.direction||"DU"==this.constants.hierarchicalLayout.direction?this.constants.hierarchicalLayout.levelSeparation*=-1:this.constants.hierarchicalLayout.levelSeparation=Math.abs(this.constants.hierarchicalLayout.levelSeparation);var t,e,i=0,s=!1,n=!1;for(e in this.nodes)this.nodes.hasOwnProperty(e)&&(t=this.nodes[e],-1!=t.level?s=!0:n=!0,is&&(o.xFixed=!1,o.x=i[o.level].minPos,r=!0):o.yFixed&&o.level>s&&(o.yFixed=!1,o.y=i[o.level].minPos,r=!0),1==r&&(i[o.level].minPos+=i[o.level].nodeSpacing,o.edges.length>1&&this._placeBranchNodes(o.edges,o.id,i,o.level))}},_setLevel:function(t,e,i){for(var s=0;st)&&(n.level=t,e.length>1&&this._setLevel(t+1,n.edges,n.id))}},_restoreNodes:function(){for(nodeId in this.nodes)this.nodes.hasOwnProperty(nodeId)&&(this.nodes[nodeId].xFixed=!1,this.nodes[nodeId].yFixed=!1)}},manipulationMixin={_clearManipulatorBar:function(){for(;this.manipulationDiv.hasChildNodes();)this.manipulationDiv.removeChild(this.manipulationDiv.firstChild)},_restoreOverloadedFunctions:function(){for(var t in this.cachedFunctions)this.cachedFunctions.hasOwnProperty(t)&&(this[t]=this.cachedFunctions[t])},_toggleEditMode:function(){this.editMode=!this.editMode;var t=document.getElementById("graph-manipulationDiv"),e=document.getElementById("graph-manipulation-closeDiv"),i=document.getElementById("graph-manipulation-editMode");1==this.editMode?(t.style.display="block",e.style.display="block",i.style.display="none",e.onclick=this._toggleEditMode.bind(this)):(t.style.display="none",e.style.display="none",i.style.display="block",e.onclick=null),this._createManipulatorBar()},_createManipulatorBar:function(){if(this.boundFunction&&this.off("select",this.boundFunction),this._restoreOverloadedFunctions(),this.freezeSimulation=!1,this.blockConnectingEdgeSelection=!1,this.forceAppendSelection=!1,1==this.editMode){for(;this.manipulationDiv.hasChildNodes();)this.manipulationDiv.removeChild(this.manipulationDiv.firstChild);this.manipulationDiv.innerHTML="Add Node
Add Link",1==this._getSelectedNodeCount()&&this.triggerFunctions.edit&&(this.manipulationDiv.innerHTML+="
Edit Node"),0==this._selectionIsEmpty()&&(this.manipulationDiv.innerHTML+="
Delete selected");var t=document.getElementById("graph-manipulate-addNode");t.onclick=this._createAddNodeToolbar.bind(this);var e=document.getElementById("graph-manipulate-connectNode");if(e.onclick=this._createAddEdgeToolbar.bind(this),1==this._getSelectedNodeCount()&&this.triggerFunctions.edit){var i=document.getElementById("graph-manipulate-editNode");i.onclick=this._editNode.bind(this)}if(0==this._selectionIsEmpty()){var s=document.getElementById("graph-manipulate-delete");s.onclick=this._deleteSelected.bind(this)}var n=document.getElementById("graph-manipulation-closeDiv");n.onclick=this._toggleEditMode.bind(this),this.boundFunction=this._createManipulatorBar.bind(this),this.on("select",this.boundFunction)}else{this.editModeDiv.innerHTML="Edit";var o=document.getElementById("graph-manipulate-editModeButton");o.onclick=this._toggleEditMode.bind(this)}},_createAddNodeToolbar:function(){this._clearManipulatorBar(),this.boundFunction&&this.off("select",this.boundFunction),this.manipulationDiv.innerHTML="Back
Click in an empty space to place a new node";var t=document.getElementById("graph-manipulate-back");t.onclick=this._createManipulatorBar.bind(this),this.boundFunction=this._addNode.bind(this),this.on("select",this.boundFunction)},_createAddEdgeToolbar:function(){this._clearManipulatorBar(),this._unselectAll(!0),this.freezeSimulation=!0,this.boundFunction&&this.off("select",this.boundFunction),this._unselectAll(),this.forceAppendSelection=!1,this.blockConnectingEdgeSelection=!0,this.manipulationDiv.innerHTML="Back
Click on a node and drag the edge to another node to connect them.";var t=document.getElementById("graph-manipulate-back");t.onclick=this._createManipulatorBar.bind(this),this.boundFunction=this._handleConnect.bind(this),this.on("select",this.boundFunction),this.cachedFunctions._handleTouch=this._handleTouch,this.cachedFunctions._handleOnRelease=this._handleOnRelease,this._handleTouch=this._handleConnect,this._handleOnRelease=this._finishConnect,this._redraw()},_handleConnect:function(t){if(0==this._getSelectedNodeCount()){var e=this._getNodeAt(t);null!=e&&(e.clusterSize>1?alert("Cannot create edges to a cluster."):(this._selectObject(e,!1),this.sectors.support.nodes.targetNode=new Node({id:"targetNode"},{},{},this.constants),this.sectors.support.nodes.targetNode.x=e.x,this.sectors.support.nodes.targetNode.y=e.y,this.sectors.support.nodes.targetViaNode=new Node({id:"targetViaNode"},{},{},this.constants),this.sectors.support.nodes.targetViaNode.x=e.x,this.sectors.support.nodes.targetViaNode.y=e.y,this.sectors.support.nodes.targetViaNode.parentEdgeId="connectionEdge",this.edges.connectionEdge=new Edge({id:"connectionEdge",from:e.id,to:this.sectors.support.nodes.targetNode.id},this,this.constants),this.edges.connectionEdge.from=e,this.edges.connectionEdge.connected=!0,this.edges.connectionEdge.smooth=!0,this.edges.connectionEdge.selected=!0,this.edges.connectionEdge.to=this.sectors.support.nodes.targetNode,this.edges.connectionEdge.via=this.sectors.support.nodes.targetViaNode,this.cachedFunctions._handleOnDrag=this._handleOnDrag,this._handleOnDrag=function(t){var e=this._getPointer(t.gesture.center);this.sectors.support.nodes.targetNode.x=this._canvasToX(e.x),this.sectors.support.nodes.targetNode.y=this._canvasToY(e.y),this.sectors.support.nodes.targetViaNode.x=.5*(this._canvasToX(e.x)+this.edges.connectionEdge.from.x),this.sectors.support.nodes.targetViaNode.y=this._canvasToY(e.y)},this.moving=!0,this.start()))}},_finishConnect:function(t){if(1==this._getSelectedNodeCount()){this._handleOnDrag=this.cachedFunctions._handleOnDrag,delete this.cachedFunctions._handleOnDrag;var e=this.edges.connectionEdge.fromId;delete this.edges.connectionEdge,delete this.sectors.support.nodes.targetNode,delete this.sectors.support.nodes.targetViaNode;var i=this._getNodeAt(t);null!=i&&(i.clusterSize>1?alert("Cannot create edges to a cluster."):(this._createEdge(e,i.id),this._createManipulatorBar())),this._unselectAll()}},_addNode:function(){if(this._selectionIsEmpty()&&1==this.editMode){var t=this._pointerToPositionObject(this.pointerPosition),e={id:util.randomUUID(),x:t.left,y:t.top,label:"new",allowedToMoveX:!0,allowedToMoveY:!0};if(this.triggerFunctions.add)if(2==this.triggerFunctions.add.length){var i=this;this.triggerFunctions.add(e,function(t){i.nodesData.add(t),i._createManipulatorBar(),i.moving=!0,i.start()})}else alert("The function for add does not support two arguments (data,callback)."),this._createManipulatorBar(),this.moving=!0,this.start();else this.nodesData.add(e),this._createManipulatorBar(),this.moving=!0,this.start()}},_createEdge:function(t,e){if(1==this.editMode){var i={from:t,to:e};if(this.triggerFunctions.connect)if(2==this.triggerFunctions.connect.length){var s=this;this.triggerFunctions.connect(i,function(t){s.edgesData.add(t),s.moving=!0,s.start()})}else alert("The function for connect does not support two arguments (data,callback)."),this.moving=!0,this.start();else this.edgesData.add(i),this.moving=!0,this.start()}},_editNode:function(){if(this.triggerFunctions.edit&&1==this.editMode){var t=this._getSelectedNode(),e={id:t.id,label:t.label,group:t.group,shape:t.shape,color:{background:t.color.background,border:t.color.border,highlight:{background:t.color.highlight.background,border:t.color.highlight.border}}};if(2==this.triggerFunctions.edit.length){var i=this;this.triggerFunctions.edit(e,function(t){i.nodesData.update(t),i._createManipulatorBar(),i.moving=!0,i.start()})}else alert("The function for edit does not support two arguments (data, callback).")}else alert("No edit function has been bound to this button.")},_deleteSelected:function(){if(!this._selectionIsEmpty()&&1==this.editMode)if(this._clusterInSelection())alert("Clusters cannot be deleted.");else{var t=this.getSelectedNodes(),e=this.getSelectedEdges();if(this.triggerFunctions.delete){var i=this,s={nodes:t,edges:e};(this.triggerFunctions.delete.length=2)?this.triggerFunctions.delete(s,function(t){i.edgesData.remove(t.edges),i.nodesData.remove(t.nodes),this._unselectAll(),i.moving=!0,i.start()}):alert("The function for edit does not support two arguments (data, callback).")}else this.edgesData.remove(e),this.nodesData.remove(t),this._unselectAll(),this.moving=!0,this.start()}}},SectorMixin={_putDataInSector:function(){this.sectors.active[this._sector()].nodes=this.nodes,this.sectors.active[this._sector()].edges=this.edges,this.sectors.active[this._sector()].nodeIndices=this.nodeIndices},_switchToSector:function(t,e){void 0===e||"active"==e?this._switchToActiveSector(t):this._switchToFrozenSector(t)},_switchToActiveSector:function(t){this.nodeIndices=this.sectors.active[t].nodeIndices,this.nodes=this.sectors.active[t].nodes,this.edges=this.sectors.active[t].edges},_switchToSupportSector:function(){this.nodeIndices=this.sectors.support.nodeIndices,this.nodes=this.sectors.support.nodes,this.edges=this.sectors.support.edges},_switchToFrozenSector:function(t){this.nodeIndices=this.sectors.frozen[t].nodeIndices,this.nodes=this.sectors.frozen[t].nodes,this.edges=this.sectors.frozen[t].edges},_loadLatestSector:function(){this._switchToSector(this._sector())},_sector:function(){return this.activeSector[this.activeSector.length-1]},_previousSector:function(){if(this.activeSector.length>1)return this.activeSector[this.activeSector.length-2];throw new TypeError("there are not enough sectors in the this.activeSector array.")},_setActiveSector:function(t){this.activeSector.push(t)},_forgetLastSector:function(){this.activeSector.pop()},_createNewSector:function(t){this.sectors.active[t]={nodes:{},edges:{},nodeIndices:[],formationScale:this.scale,drawingNode:void 0},this.sectors.active[t].drawingNode=new Node({id:t,color:{background:"#eaefef",border:"495c5e"}},{},{},this.constants),this.sectors.active[t].drawingNode.clusterSize=2},_deleteActiveSector:function(t){delete this.sectors.active[t]},_deleteFrozenSector:function(t){delete this.sectors.frozen[t]},_freezeSector:function(t){this.sectors.frozen[t]=this.sectors.active[t],this._deleteActiveSector(t)},_activateSector:function(t){this.sectors.active[t]=this.sectors.frozen[t],this._deleteFrozenSector(t)},_mergeThisWithFrozen:function(t){for(var e in this.nodes)this.nodes.hasOwnProperty(e)&&(this.sectors.frozen[t].nodes[e]=this.nodes[e]);for(var i in this.edges)this.edges.hasOwnProperty(i)&&(this.sectors.frozen[t].edges[i]=this.edges[i]);for(var s=0;s1?this[t](s[0],s[1]):this[t](e)}this._loadLatestSector()},_doInSupportSector:function(t,e){if(void 0===e)this._switchToSupportSector(),this[t]();else{this._switchToSupportSector();var i=Array.prototype.splice.call(arguments,1);i.length>1?this[t](i[0],i[1]):this[t](e)}this._loadLatestSector()},_doInAllFrozenSectors:function(t,e){if(void 0===e)for(var i in this.sectors.frozen)this.sectors.frozen.hasOwnProperty(i)&&(this._switchToFrozenSector(i),this[t]());else for(var i in this.sectors.frozen)if(this.sectors.frozen.hasOwnProperty(i)){this._switchToFrozenSector(i);var s=Array.prototype.splice.call(arguments,1);s.length>1?this[t](s[0],s[1]):this[t](e)}this._loadLatestSector()},_doInAllSectors:function(t,e){var i=Array.prototype.splice.call(arguments,1);void 0===e?(this._doInAllActiveSectors(t),this._doInAllFrozenSectors(t)):i.length>1?(this._doInAllActiveSectors(t,i[0],i[1]),this._doInAllFrozenSectors(t,i[0],i[1])):(this._doInAllActiveSectors(t,e),this._doInAllFrozenSectors(t,e))},_clearNodeIndexList:function(){var t=this._sector();this.sectors.active[t].nodeIndices=[],this.nodeIndices=this.sectors.active[t].nodeIndices},_drawSectorNodes:function(t,e){var i,s=1e9,n=-1e9,o=1e9,r=-1e9;for(var a in this.sectors[e])if(this.sectors[e].hasOwnProperty(a)&&void 0!==this.sectors[e][a].drawingNode){this._switchToSector(a,e),s=1e9,n=-1e9,o=1e9,r=-1e9;for(var h in this.nodes)this.nodes.hasOwnProperty(h)&&(i=this.nodes[h],i.resize(t),o>i.x-.5*i.width&&(o=i.x-.5*i.width),ri.y-.5*i.height&&(s=i.y-.5*i.height),nt&&s>n;)n%3==0?(this.forceAggregateHubs(!0),this.normalizeClusterLevels()):this.increaseClusterLevel(),i=this.nodeIndices.length,n+=1;n>0&&1==e&&this.repositionNodes(),this._updateCalculationNodes()},openCluster:function(t){var e=this.moving;if(t.clusterSize>this.constants.clustering.sectorThreshold&&this._nodeInActiveArea(t)&&("default"!=this._sector()||1!=this.nodeIndices.length)){this._addSector(t);for(var i=0;this.nodeIndices.lengthi;)this.decreaseClusterLevel(),i+=1}else this._expandClusterNode(t,!1,!0),this._updateNodeIndexList(),this._updateDynamicEdges(),this._updateCalculationNodes(),this.updateLabels();this.moving!=e&&this.start()},updateClustersDefault:function(){1==this.constants.clustering.enabled&&this.updateClusters(0,!1,!1)},increaseClusterLevel:function(){this.updateClusters(-1,!1,!0)},decreaseClusterLevel:function(){this.updateClusters(1,!1,!0)},updateClusters:function(t,e,i,s){var n=this.moving,o=this.nodeIndices.length;this.previousScale>this.scale&&0==t&&this._collapseSector(),this.previousScale>this.scale||-1==t?this._formClusters(i):(this.previousScalethis.scale||-1==t)&&(this._aggregateHubs(i),this._updateNodeIndexList()),(this.previousScale>this.scale||-1==t)&&(this.handleChains(),this._updateNodeIndexList()),this.previousScale=this.scale,this._updateDynamicEdges(),this.updateLabels(),this.nodeIndices.lengththis.constants.clustering.chainThreshold&&this._reduceAmountOfChains(1-this.constants.clustering.chainThreshold/t)},_aggregateHubs:function(t){this._getHubSize(),this._formClustersByHub(t,!1)},forceAggregateHubs:function(t){var e=this.moving,i=this.nodeIndices.length;this._aggregateHubs(!0),this._updateNodeIndexList(),this._updateDynamicEdges(),this.updateLabels(),this.nodeIndices.length!=i&&(this.clusterSession+=1),(0==t||void 0===t)&&this.moving!=e&&this.start()},_openClustersBySize:function(){for(var t in this.nodes)if(this.nodes.hasOwnProperty(t)){var e=this.nodes[t];1==e.inView()&&(e.width*this.scale>this.constants.clustering.screenSizeThreshold*this.frame.canvas.clientWidth||e.height*this.scale>this.constants.clustering.screenSizeThreshold*this.frame.canvas.clientHeight)&&this.openCluster(e)}},_openClusters:function(t,e){for(var i=0;i1&&(t.clusterSizei)){var r=o.from,a=o.to;o.to.mass>o.from.mass&&(r=o.to,a=o.from),1==a.dynamicEdgesLength?this._addToCluster(r,a,!1):1==r.dynamicEdgesLength&&this._addToCluster(a,r,!1)}}},_forceClustersByZoom:function(){for(var t in this.nodes)if(this.nodes.hasOwnProperty(t)){var e=this.nodes[t];if(1==e.dynamicEdgesLength&&0!=e.dynamicEdges.length){var i=e.dynamicEdges[0],s=i.toId==e.id?this.nodes[i.fromId]:this.nodes[i.toId];e.id!=s.id&&(s.mass>e.mass?this._addToCluster(s,e,!0):this._addToCluster(e,s,!0))}}},_clusterToSmallestNeighbour:function(t){for(var e=-1,i=null,s=0;sn.clusterSessions.length&&(e=n.clusterSessions.length,i=n)}null!=n&&void 0!==this.nodes[n.id]&&this._addToCluster(n,t,!0)},_formClustersByHub:function(t,e){for(var i in this.nodes)this.nodes.hasOwnProperty(i)&&this._formClusterFromHub(this.nodes[i],t,e)},_formClusterFromHub:function(t,e,i,s){if(void 0===s&&(s=0),t.dynamicEdgesLength>=this.hubThreshold&&0==i||t.dynamicEdgesLength==this.hubThreshold&&1==i){for(var n,o,r,a=this.constants.clustering.clusterEdgeThreshold/this.scale,h=!1,d=[],c=t.dynamicEdges.length,l=0;c>l;l++)d.push(t.dynamicEdges[l].id);if(0==e)for(h=!1,l=0;c>l;l++){var u=this.edges[d[l]];if(void 0!==u&&u.connected&&u.toId!=u.fromId&&(n=u.to.x-u.from.x,o=u.to.y-u.from.y,r=Math.sqrt(n*n+o*o),a>r)){h=!0;break}}if(!e&&h||e)for(l=0;c>l;l++)if(u=this.edges[d[l]],void 0!==u){var p=this.nodes[u.fromId==t.id?u.toId:u.fromId];p.dynamicEdges.length<=this.hubThreshold+s&&p.id!=t.id&&this._addToCluster(t,p,e)}}},_addToCluster:function(t,e,i){t.containedNodes[e.id]=e;for(var s=0;s1)for(var s=0;s1&&(e.label="[".concat(String(e.clusterSize),"]"))}for(t in this.nodes)this.nodes.hasOwnProperty(t)&&(e=this.nodes[t],1==e.clusterSize&&(e.label=void 0!==e.originalLabel?e.originalLabel:String(e.id)))},normalizeClusterLevels:function(){var t=0,e=1e9,i=0;for(var s in this.nodes)this.nodes.hasOwnProperty(s)&&(i=this.nodes[s].clusterSessions.length,i>t&&(t=i),e>i&&(e=i));if(t-e>this.constants.clustering.clusterLevelDifference){var n=this.nodeIndices.length,o=t-this.constants.clustering.clusterLevelDifference;for(var s in this.nodes)this.nodes.hasOwnProperty(s)&&this.nodes[s].clusterSessions.lengths&&(s=o.dynamicEdgesLength),t+=o.dynamicEdgesLength,e+=Math.pow(o.dynamicEdgesLength,2),i+=1}t/=i,e/=i;var r=e-Math.pow(t,2),a=Math.sqrt(r);this.hubThreshold=Math.floor(t+2*a),this.hubThreshold>s&&(this.hubThreshold=s)},_reduceAmountOfChains:function(t){this.hubThreshold=2;var e=Math.floor(this.nodeIndices.length*t);for(var i in this.nodes)this.nodes.hasOwnProperty(i)&&2==this.nodes[i].dynamicEdgesLength&&this.nodes[i].dynamicEdges.length>=2&&e>0&&(this._formClusterFromHub(this.nodes[i],!0,!0,1),e-=1)},_getChainFraction:function(){var t=0,e=0;for(var i in this.nodes)this.nodes.hasOwnProperty(i)&&(2==this.nodes[i].dynamicEdgesLength&&this.nodes[i].dynamicEdges.length>=2&&(t+=1),e+=1);return t/e}},SelectionMixin={_getNodesOverlappingWith:function(t,e){var i=this.nodes;for(var s in i)i.hasOwnProperty(s)&&i[s].isOverlappingWith(t)&&e.push(s)},_getAllNodesOverlappingWith:function(t){var e=[];return this._doInAllActiveSectors("_getNodesOverlappingWith",t,e),e},_pointerToPositionObject:function(t){var e=this._canvasToX(t.x),i=this._canvasToY(t.y);return{left:e,top:i,right:e,bottom:i}},_getNodeAt:function(t){var e=this._pointerToPositionObject(t),i=this._getAllNodesOverlappingWith(e);return i.length>0?this.nodes[i[i.length-1]]:null},_getEdgesOverlappingWith:function(t,e){var i=this.edges;for(var s in i)i.hasOwnProperty(s)&&i[s].isOverlappingWith(t)&&e.push(s)},_getAllEdgesOverlappingWith:function(t){var e=[];return this._doInAllActiveSectors("_getEdgesOverlappingWith",t,e),e},_getEdgeAt:function(t){var e=this._pointerToPositionObject(t),i=this._getAllEdgesOverlappingWith(e);return i.length>0?this.edges[i[i.length-1]]:null},_addToSelection:function(t){t instanceof Node?this.selectionObj.nodes[t.id]=t:this.selectionObj.edges[t.id]=t},_removeFromSelection:function(t){t instanceof Node?delete this.selectionObj.nodes[t.id]:delete this.selectionObj.edges[t.id]},_unselectAll:function(t){void 0===t&&(t=!1);for(var e in this.selectionObj.nodes)this.selectionObj.nodes.hasOwnProperty(e)&&this.selectionObj.nodes[e].unselect();for(var i in this.selectionObj.edges)this.selectionObj.edges.hasOwnProperty(i)&&this.selectionObj.edges[i].unselect();this.selectionObj={nodes:{},edges:{}},0==t&&this.emit("select",this.getSelection())},_unselectClusters:function(t){void 0===t&&(t=!1);for(var e in this.selectionObj.nodes)this.selectionObj.nodes.hasOwnProperty(e)&&this.selectionObj.nodes[e].clusterSize>1&&(this.selectionObj.nodes[e].unselect(),this._removeFromSelection(this.selectionObj.nodes[e]));0==t&&this.emit("select",this.getSelection())},_getSelectedNodeCount:function(){var t=0;for(var e in this.selectionObj.nodes)this.selectionObj.nodes.hasOwnProperty(e)&&(t+=1);return t},_getSelectedNode:function(){for(var t in this.selectionObj.nodes)if(this.selectionObj.nodes.hasOwnProperty(t))return this.selectionObj.nodes[t];return null},_getSelectedEdgeCount:function(){var t=0;for(var e in this.selectionObj.edges)this.selectionObj.edges.hasOwnProperty(e)&&(t+=1);return t},_getSelectedObjectCount:function(){var t=0;for(var e in this.selectionObj.nodes)this.selectionObj.nodes.hasOwnProperty(e)&&(t+=1);for(var i in this.selectionObj.edges)this.selectionObj.edges.hasOwnProperty(i)&&(t+=1);return t},_selectionIsEmpty:function(){for(var t in this.selectionObj.nodes)if(this.selectionObj.nodes.hasOwnProperty(t))return!1;for(var e in this.selectionObj.edges)if(this.selectionObj.edges.hasOwnProperty(e))return!1;return!0},_clusterInSelection:function(){for(var t in this.selectionObj.nodes)if(this.selectionObj.nodes.hasOwnProperty(t)&&this.selectionObj.nodes[t].clusterSize>1)return!0;return!1},_selectConnectedEdges:function(t){for(var e=0;ee;e++){s=t[e];var n=this.nodes[s];if(!n)throw new RangeError('Node with id "'+s+'" not found');this._selectObject(n,!0,!0)}this.redraw()},_updateSelection:function(){for(var t in this.selectionObj.nodes)this.selectionObj.nodes.hasOwnProperty(t)&&(this.nodes.hasOwnProperty(t)||delete this.selectionObj.nodes[t]);for(var e in this.selectionObj.edges)this.selectionObj.edges.hasOwnProperty(e)&&(this.edges.hasOwnProperty(e)||delete this.selectionObj.edges[e])}},NavigationMixin={_cleanNavigation:function(){var t=document.getElementById("graph-navigation_wrapper");null!=t&&this.containerElement.removeChild(t),document.onmouseup=null},_loadNavigationElements:function(){this._cleanNavigation(),this.navigationDivs={};var t=["up","down","left","right","zoomIn","zoomOut","zoomExtends"],e=["_moveUp","_moveDown","_moveLeft","_moveRight","_zoomIn","_zoomOut","zoomExtent"];this.navigationDivs.wrapper=document.createElement("div"),this.navigationDivs.wrapper.id="graph-navigation_wrapper",this.navigationDivs.wrapper.style.position="absolute",this.navigationDivs.wrapper.style.width=this.frame.canvas.clientWidth+"px",this.navigationDivs.wrapper.style.height=this.frame.canvas.clientHeight+"px",this.containerElement.insertBefore(this.navigationDivs.wrapper,this.frame);for(var i=0;it.x&&(s=t.x),nt.y&&(e=t.y),i=this.constants.clustering.initialMaxNodes?49.07548/(n+142.05338)+91444e-8:12.662/(n+7.4147)+.0964822:1==this.constants.clustering.enabled&&n>=this.constants.clustering.initialMaxNodes?77.5271985/(n+187.266146)+476710517e-13:30.5062972/(n+19.93597763)+.08413486;var o=Math.min(this.frame.canvas.clientWidth/600,this.frame.canvas.clientHeight/600);i*=o}else{var r=1.1*(Math.abs(s.minX)+Math.abs(s.maxX)),a=1.1*(Math.abs(s.minY)+Math.abs(s.maxY)),h=this.frame.canvas.clientWidth/r,d=this.frame.canvas.clientHeight/a;i=d>=h?h:d}i>1&&(i=1),this._setScale(i),this._centerGraph(s),0==e&&(this.moving=!0,this.start())},Graph.prototype._updateNodeIndexList=function(){this._clearNodeIndexList();for(var t in this.nodes)this.nodes.hasOwnProperty(t)&&this.nodeIndices.push(t)},Graph.prototype.setData=function(t,e){if(void 0===e&&(e=!1),t&&t.dot&&(t.nodes||t.edges))throw new SyntaxError('Data must contain either parameter "dot" or parameter pair "nodes" and "edges", but not both.');if(this.setOptions(t&&t.options),t&&t.dot){if(t&&t.dot){var i=vis.util.DOTToGraph(t.dot);return void this.setData(i)}}else this._setNodes(t&&t.nodes),this._setEdges(t&&t.edges);this._putDataInSector(),e||(this.stabilize&&this._stabilize(),this.start())},Graph.prototype.setOptions=function(t){if(t){var e;if(void 0!==t.width&&(this.width=t.width),void 0!==t.height&&(this.height=t.height),void 0!==t.stabilize&&(this.stabilize=t.stabilize),void 0!==t.selectable&&(this.selectable=t.selectable),void 0!==t.smoothCurves&&(this.constants.smoothCurves=t.smoothCurves),void 0!==t.freezeForStabilization&&(this.constants.freezeForStabilization=t.freezeForStabilization),void 0!==t.configurePhysics&&(this.constants.configurePhysics=t.configurePhysics),void 0!==t.stabilizationIterations&&(this.constants.stabilizationIterations=t.stabilizationIterations),t.onAdd&&(this.triggerFunctions.add=t.onAdd),t.onEdit&&(this.triggerFunctions.edit=t.onEdit),t.onConnect&&(this.triggerFunctions.connect=t.onConnect),t.onDelete&&(this.triggerFunctions.delete=t.onDelete),t.physics){if(t.physics.barnesHut){this.constants.physics.barnesHut.enabled=!0;for(e in t.physics.barnesHut)t.physics.barnesHut.hasOwnProperty(e)&&(this.constants.physics.barnesHut[e]=t.physics.barnesHut[e])}if(t.physics.repulsion){this.constants.physics.barnesHut.enabled=!1;for(e in t.physics.repulsion)t.physics.repulsion.hasOwnProperty(e)&&(this.constants.physics.repulsion[e]=t.physics.repulsion[e])}}if(t.hierarchicalLayout){this.constants.hierarchicalLayout.enabled=!0;for(e in t.hierarchicalLayout)t.hierarchicalLayout.hasOwnProperty(e)&&(this.constants.hierarchicalLayout[e]=t.hierarchicalLayout[e])}else void 0!==t.hierarchicalLayout&&(this.constants.hierarchicalLayout.enabled=!1);if(t.clustering){this.constants.clustering.enabled=!0;for(e in t.clustering)t.clustering.hasOwnProperty(e)&&(this.constants.clustering[e]=t.clustering[e])}else void 0!==t.clustering&&(this.constants.clustering.enabled=!1);if(t.navigation){this.constants.navigation.enabled=!0;for(e in t.navigation)t.navigation.hasOwnProperty(e)&&(this.constants.navigation[e]=t.navigation[e])}else void 0!==t.navigation&&(this.constants.navigation.enabled=!1);if(t.keyboard){this.constants.keyboard.enabled=!0;for(e in t.keyboard)t.keyboard.hasOwnProperty(e)&&(this.constants.keyboard[e]=t.keyboard[e])}else void 0!==t.keyboard&&(this.constants.keyboard.enabled=!1);if(t.dataManipulation){this.constants.dataManipulation.enabled=!0;for(e in t.dataManipulation)t.dataManipulation.hasOwnProperty(e)&&(this.constants.dataManipulation[e]=t.dataManipulation[e])}else void 0!==t.dataManipulation&&(this.constants.dataManipulation.enabled=!1);if(t.edges){for(e in t.edges)t.edges.hasOwnProperty(e)&&"object"!=typeof t.edges[e]&&(this.constants.edges[e]=t.edges[e]);void 0!==t.edges.color&&(util.isString(t.edges.color)?(this.constants.edges.color.color=t.edges.color,this.constants.edges.color.highlight=t.edges.color):(void 0!==t.edges.color.color&&(this.constants.edges.color.color=t.edges.color.color),void 0!==t.edges.color.highlight&&(this.constants.edges.color.highlight=t.edges.color.highlight))),t.edges.fontColor||void 0!==t.edges.color&&(util.isString(t.edges.color)?this.constants.edges.fontColor=t.edges.color:void 0!==t.edges.color.color&&(this.constants.edges.fontColor=t.edges.color.color)),t.edges.dash&&(void 0!==t.edges.dash.length&&(this.constants.edges.dash.length=t.edges.dash.length),void 0!==t.edges.dash.gap&&(this.constants.edges.dash.gap=t.edges.dash.gap),void 0!==t.edges.dash.altLength&&(this.constants.edges.dash.altLength=t.edges.dash.altLength))}if(t.nodes){for(e in t.nodes)t.nodes.hasOwnProperty(e)&&(this.constants.nodes[e]=t.nodes[e]);t.nodes.color&&(this.constants.nodes.color=Node.parseColor(t.nodes.color))}if(t.groups)for(var i in t.groups)if(t.groups.hasOwnProperty(i)){var s=t.groups[i];this.groups.add(i,s)}}this._loadPhysicsSystem(),this._loadNavigationControls(),this._loadManipulationSystem(),this._configureSmoothCurves(),this._createKeyBinds(),this.setSize(this.width,this.height),this._setTranslation(this.frame.clientWidth/2,this.frame.clientHeight/2),this._setScale(1),this._redraw()},Graph.prototype._create=function(){for(;this.containerElement.hasChildNodes();)this.containerElement.removeChild(this.containerElement.firstChild);if(this.frame=document.createElement("div"),this.frame.className="graph-frame",this.frame.style.position="relative",this.frame.style.overflow="hidden",this.frame.style.zIndex="1",this.frame.canvas=document.createElement("canvas"),this.frame.canvas.style.position="relative",this.frame.appendChild(this.frame.canvas),!this.frame.canvas.getContext){var t=document.createElement("DIV");t.style.color="red",t.style.fontWeight="bold",t.style.padding="10px",t.innerHTML="Error: your browser does not support HTML canvas",this.frame.canvas.appendChild(t)}var e=this;this.drag={},this.pinch={},this.hammer=Hammer(this.frame.canvas,{prevent_default:!0}),this.hammer.on("tap",e._onTap.bind(e)),this.hammer.on("doubletap",e._onDoubleTap.bind(e)),this.hammer.on("hold",e._onHold.bind(e)),this.hammer.on("pinch",e._onPinch.bind(e)),this.hammer.on("touch",e._onTouch.bind(e)),this.hammer.on("dragstart",e._onDragStart.bind(e)),this.hammer.on("drag",e._onDrag.bind(e)),this.hammer.on("dragend",e._onDragEnd.bind(e)),this.hammer.on("release",e._onRelease.bind(e)),this.hammer.on("mousewheel",e._onMouseWheel.bind(e)),this.hammer.on("DOMMouseScroll",e._onMouseWheel.bind(e)),this.hammer.on("mousemove",e._onMouseMoveTitle.bind(e)),this.containerElement.appendChild(this.frame)},Graph.prototype._createKeyBinds=function(){var t=this;this.mousetrap=mousetrap,this.mousetrap.reset(),1==this.constants.keyboard.enabled&&(this.mousetrap.bind("up",this._moveUp.bind(t),"keydown"),this.mousetrap.bind("up",this._yStopMoving.bind(t),"keyup"),this.mousetrap.bind("down",this._moveDown.bind(t),"keydown"),this.mousetrap.bind("down",this._yStopMoving.bind(t),"keyup"),this.mousetrap.bind("left",this._moveLeft.bind(t),"keydown"),this.mousetrap.bind("left",this._xStopMoving.bind(t),"keyup"),this.mousetrap.bind("right",this._moveRight.bind(t),"keydown"),this.mousetrap.bind("right",this._xStopMoving.bind(t),"keyup"),this.mousetrap.bind("=",this._zoomIn.bind(t),"keydown"),this.mousetrap.bind("=",this._stopZoom.bind(t),"keyup"),this.mousetrap.bind("-",this._zoomOut.bind(t),"keydown"),this.mousetrap.bind("-",this._stopZoom.bind(t),"keyup"),this.mousetrap.bind("[",this._zoomIn.bind(t),"keydown"),this.mousetrap.bind("[",this._stopZoom.bind(t),"keyup"),this.mousetrap.bind("]",this._zoomOut.bind(t),"keydown"),this.mousetrap.bind("]",this._stopZoom.bind(t),"keyup"),this.mousetrap.bind("pageup",this._zoomIn.bind(t),"keydown"),this.mousetrap.bind("pageup",this._stopZoom.bind(t),"keyup"),this.mousetrap.bind("pagedown",this._zoomOut.bind(t),"keydown"),this.mousetrap.bind("pagedown",this._stopZoom.bind(t),"keyup")),1==this.constants.dataManipulation.enabled&&(this.mousetrap.bind("escape",this._createManipulatorBar.bind(t)),this.mousetrap.bind("del",this._deleteSelected.bind(t)))},Graph.prototype._getPointer=function(t){return{x:t.pageX-vis.util.getAbsoluteLeft(this.frame.canvas),y:t.pageY-vis.util.getAbsoluteTop(this.frame.canvas)}},Graph.prototype._onTouch=function(t){this.drag.pointer=this._getPointer(t.gesture.center),this.drag.pinched=!1,this.pinch.scale=this._getScale(),this._handleTouch(this.drag.pointer)},Graph.prototype._onDragStart=function(){this._handleDragStart()},Graph.prototype._handleDragStart=function(){var t=this.drag,e=this._getNodeAt(t.pointer);if(t.dragging=!0,t.selection=[],t.translation=this._getTranslation(),t.nodeId=null,null!=e){t.nodeId=e.id,e.isSelected()||this._selectObject(e,!1);for(var i in this.selectionObj.nodes)if(this.selectionObj.nodes.hasOwnProperty(i)){var s=this.selectionObj.nodes[i],n={id:s.id,node:s,x:s.x,y:s.y,xFixed:s.xFixed,yFixed:s.yFixed};s.xFixed=!0,s.yFixed=!0,t.selection.push(n)}}},Graph.prototype._onDrag=function(t){this._handleOnDrag(t)},Graph.prototype._handleOnDrag=function(t){if(!this.drag.pinched){var e=this._getPointer(t.gesture.center),i=this,s=this.drag,n=s.selection;if(n&&n.length){var o=e.x-s.pointer.x,r=e.y-s.pointer.y;n.forEach(function(t){var e=t.node;t.xFixed||(e.x=i._canvasToX(i._xToCanvas(t.x)+o)),t.yFixed||(e.y=i._canvasToY(i._yToCanvas(t.y)+r))}),this.moving||(this.moving=!0,this.start())}else{var a=e.x-this.drag.pointer.x,h=e.y-this.drag.pointer.y;this._setTranslation(this.drag.translation.x+a,this.drag.translation.y+h),this._redraw(),this.moved=!0}}},Graph.prototype._onDragEnd=function(){this.drag.dragging=!1;var t=this.drag.selection;t&&t.forEach(function(t){t.node.xFixed=t.xFixed,t.node.yFixed=t.yFixed})},Graph.prototype._onTap=function(t){var e=this._getPointer(t.gesture.center);this.pointerPosition=e,this._handleTap(e)},Graph.prototype._onDoubleTap=function(t){var e=this._getPointer(t.gesture.center);this._handleDoubleTap(e)},Graph.prototype._onHold=function(t){var e=this._getPointer(t.gesture.center);this.pointerPosition=e,this._handleOnHold(e)},Graph.prototype._onRelease=function(t){var e=this._getPointer(t.gesture.center);this._handleOnRelease(e)},Graph.prototype._onPinch=function(t){var e=this._getPointer(t.gesture.center);this.drag.pinched=!0,"scale"in this.pinch||(this.pinch.scale=1);var i=this.pinch.scale*t.gesture.scale;this._zoom(i,e)},Graph.prototype._zoom=function(t,e){var i=this._getScale();1e-5>t&&(t=1e-5),t>10&&(t=10);var s=this._getTranslation(),n=t/i,o=(1-n)*e.x+s.x*n,r=(1-n)*e.y+s.y*n;return this.areaCenter={x:this._canvasToX(e.x),y:this._canvasToY(e.y)},this._setScale(t),this._setTranslation(o,r),this.updateClustersDefault(),this._redraw(),t},Graph.prototype._onMouseWheel=function(t){var e=0;if(t.wheelDelta?e=t.wheelDelta/120:t.detail&&(e=-t.detail/3),e){var i=this._getScale(),s=e/10;0>e&&(s/=1-s),i*=1+s;var n=util.fakeGesture(this,t),o=this._getPointer(n.center);this._zoom(i,o)}t.preventDefault()},Graph.prototype._onMouseMoveTitle=function(t){var e=util.fakeGesture(this,t),i=this._getPointer(e.center);this.popupNode&&this._checkHidePopup(i);var s=this,n=function(){s._checkShowPopup(i)};this.popupTimer&&clearInterval(this.popupTimer),this.drag.dragging||(this.popupTimer=setTimeout(n,300))},Graph.prototype._checkShowPopup=function(t){var e,i={left:this._canvasToX(t.x),top:this._canvasToY(t.y),right:this._canvasToX(t.x),bottom:this._canvasToY(t.y)},s=this.popupNode;if(void 0==this.popupNode){var n=this.nodes;for(e in n)if(n.hasOwnProperty(e)){var o=n[e];if(void 0!==o.getTitle()&&o.isOverlappingWith(i)){this.popupNode=o;break}}}if(void 0===this.popupNode){var r=this.edges;for(e in r)if(r.hasOwnProperty(e)){var a=r[e];if(a.connected&&void 0!==a.getTitle()&&a.isOverlappingWith(i)){this.popupNode=a;break}}}if(this.popupNode){if(this.popupNode!=s){var h=this;h.popup||(h.popup=new Popup(h.frame)),h.popup.setPosition(t.x-3,t.y-3),h.popup.setText(h.popupNode.getTitle()),h.popup.show()}}else this.popup&&this.popup.hide()},Graph.prototype._checkHidePopup=function(t){this.popupNode&&this._getNodeAt(t)||(this.popupNode=void 0,this.popup&&this.popup.hide())},Graph.prototype.setSize=function(t,e){this.frame.style.width=t,this.frame.style.height=e,this.frame.canvas.style.width="100%",this.frame.canvas.style.height="100%",this.frame.canvas.width=this.frame.canvas.clientWidth,this.frame.canvas.height=this.frame.canvas.clientHeight,void 0!==this.manipulationDiv&&(this.manipulationDiv.style.width=this.frame.canvas.clientWidth+"px"),void 0!==this.navigationDivs&&void 0!==this.navigationDivs.wrapper&&(this.navigationDivs.wrapper.style.width=this.frame.canvas.clientWidth+"px",this.navigationDivs.wrapper.style.height=this.frame.canvas.clientHeight+"px"),this.emit("resize",{width:this.frame.canvas.width,height:this.frame.canvas.height})},Graph.prototype._setNodes=function(t){var e=this.nodesData;if(t instanceof DataSet||t instanceof DataView)this.nodesData=t;else if(t instanceof Array)this.nodesData=new DataSet,this.nodesData.add(t);else{if(t)throw new TypeError("Array or DataSet expected");this.nodesData=new DataSet}if(e&&util.forEach(this.nodesListeners,function(t,i){e.off(i,t)}),this.nodes={},this.nodesData){var i=this;util.forEach(this.nodesListeners,function(t,e){i.nodesData.on(e,t)});var s=this.nodesData.getIds();this._addNodes(s)}this._updateSelection()},Graph.prototype._addNodes=function(t){for(var e,i=0,s=t.length;s>i;i++){e=t[i];var n=this.nodesData.get(e),o=new Node(n,this.images,this.groups,this.constants);if(this.nodes[e]=o,!(0!=o.xFixed&&0!=o.yFixed||null!==o.x&&null!==o.y)){var r=1*t.length,a=2*Math.PI*Math.random();0==o.xFixed&&(o.x=r*Math.cos(a)),0==o.yFixed&&(o.y=r*Math.sin(a))}this.moving=!0}this._updateNodeIndexList(),this._updateCalculationNodes(),this._reconnectEdges(),this._updateValueRange(this.nodes),this.updateLabels()},Graph.prototype._updateNodes=function(t){for(var e=this.nodes,i=this.nodesData,s=0,n=t.length;n>s;s++){var o=t[s],r=e[o],a=i.get(o);r?r.setProperties(a,this.constants):(r=new Node(properties,this.images,this.groups,this.constants),e[o]=r,r.isFixed()||(this.moving=!0))}this._updateNodeIndexList(),this._reconnectEdges(),this._updateValueRange(e)},Graph.prototype._removeNodes=function(t){for(var e=this.nodes,i=0,s=t.length;s>i;i++){var n=t[i];delete e[n]}this._updateNodeIndexList(),this._updateCalculationNodes(),this._reconnectEdges(),this._updateSelection(),this._updateValueRange(e)},Graph.prototype._setEdges=function(t){var e=this.edgesData;if(t instanceof DataSet||t instanceof DataView)this.edgesData=t;else if(t instanceof Array)this.edgesData=new DataSet,this.edgesData.add(t);else{if(t)throw new TypeError("Array or DataSet expected");this.edgesData=new DataSet}if(e&&util.forEach(this.edgesListeners,function(t,i){e.off(i,t)}),this.edges={},this.edgesData){var i=this;util.forEach(this.edgesListeners,function(t,e){i.edgesData.on(e,t)});var s=this.edgesData.getIds();this._addEdges(s)}this._reconnectEdges()},Graph.prototype._addEdges=function(t){for(var e=this.edges,i=this.edgesData,s=0,n=t.length;n>s;s++){var o=t[s],r=e[o];r&&r.disconnect();var a=i.get(o,{showInternalIds:!0});e[o]=new Edge(a,this,this.constants)}this.moving=!0,this._updateValueRange(e),this._createBezierNodes(),this._updateCalculationNodes()},Graph.prototype._updateEdges=function(t){for(var e=this.edges,i=this.edgesData,s=0,n=t.length;n>s;s++){var o=t[s],r=i.get(o),a=e[o];a?(a.disconnect(),a.setProperties(r,this.constants),a.connect()):(a=new Edge(r,this,this.constants),this.edges[o]=a)}this._createBezierNodes(),this.moving=!0,this._updateValueRange(e)},Graph.prototype._removeEdges=function(t){for(var e=this.edges,i=0,s=t.length;s>i;i++){var n=t[i],o=e[n];o&&(null!=o.via&&delete this.sectors.support.nodes[o.via.id],o.disconnect(),delete e[n])}this.moving=!0,this._updateValueRange(e),this._updateCalculationNodes()},Graph.prototype._reconnectEdges=function(){var t,e=this.nodes,i=this.edges;for(t in e)e.hasOwnProperty(t)&&(e[t].edges=[]);for(t in i)if(i.hasOwnProperty(t)){var s=i[t];s.from=null,s.to=null,s.connect()}},Graph.prototype._updateValueRange=function(t){var e,i=void 0,s=void 0;for(e in t)if(t.hasOwnProperty(e)){var n=t[e].getValue();void 0!==n&&(i=void 0===i?n:Math.min(n,i),s=void 0===s?n:Math.max(n,s))}if(void 0!==i&&void 0!==s)for(e in t)t.hasOwnProperty(e)&&t[e].setValueRange(i,s)},Graph.prototype.redraw=function(){this.setSize(this.width,this.height),this._redraw()},Graph.prototype._redraw=function(){var t=this.frame.canvas.getContext("2d"),e=this.frame.canvas.width,i=this.frame.canvas.height;t.clearRect(0,0,e,i),t.save(),t.translate(this.translation.x,this.translation.y),t.scale(this.scale,this.scale),this.canvasTopLeft={x:this._canvasToX(0),y:this._canvasToY(0)},this.canvasBottomRight={x:this._canvasToX(this.frame.canvas.clientWidth),y:this._canvasToY(this.frame.canvas.clientHeight)},this._doInAllSectors("_drawAllSectorNodes",t),this._doInAllSectors("_drawEdges",t),this._doInAllSectors("_drawNodes",t,!1),t.restore()},Graph.prototype._setTranslation=function(t,e){void 0===this.translation&&(this.translation={x:0,y:0}),void 0!==t&&(this.translation.x=t),void 0!==e&&(this.translation.y=e)},Graph.prototype._getTranslation=function(){return{x:this.translation.x,y:this.translation.y}},Graph.prototype._setScale=function(t){this.scale=t},Graph.prototype._getScale=function(){return this.scale},Graph.prototype._canvasToX=function(t){return(t-this.translation.x)/this.scale},Graph.prototype._xToCanvas=function(t){return t*this.scale+this.translation.x},Graph.prototype._canvasToY=function(t){return(t-this.translation.y)/this.scale},Graph.prototype._yToCanvas=function(t){return t*this.scale+this.translation.y},Graph.prototype._drawNodes=function(t,e){void 0===e&&(e=!1);var i=this.nodes,s=[];for(var n in i)i.hasOwnProperty(n)&&(i[n].setScaleAndPos(this.scale,this.canvasTopLeft,this.canvasBottomRight),i[n].isSelected()?s.push(n):(i[n].inArea()||e)&&i[n].draw(t));for(var o=0,r=s.length;r>o;o++)(i[s[o]].inArea()||e)&&i[s[o]].draw(t)},Graph.prototype._drawEdges=function(t){var e=this.edges;for(var i in e)if(e.hasOwnProperty(i)){var s=e[i];s.setScale(this.scale),s.connected&&e[i].draw(t)}},Graph.prototype._stabilize=function(){1==this.constants.freezeForStabilization&&this._freezeDefinedNodes();for(var t=0;this.moving&&t0)for(t in i)i.hasOwnProperty(t)&&i[t].discreteStepLimited(e,this.constants.maxVelocity);else for(t in i)i.hasOwnProperty(t)&&i[t].discreteStep(e);var s=this.constants.minVelocity/Math.max(this.scale,.05);this.moving=s>.5*this.constants.maxVelocity?!0:this._isMoving(s)},Graph.prototype._physicsTick=function(){this.freezeSimulation||this.moving&&(this._doInAllActiveSectors("_initializeForceCalculation"),this._doInAllActiveSectors("_discreteStepNodes"),this.constants.smoothCurves&&this._doInSupportSector("_discreteStepNodes"),this._findCenter(this._getRange()))},Graph.prototype._animationStep=function(){this.timer=void 0,this._handleNavigation(),this.start();var t=Date.now(),e=1;this._physicsTick();for(var i=Date.now()-t;is;++s)i[s].apply(this,e)}return this},i.prototype.listeners=function(t){return this._callbacks=this._callbacks||{},this._callbacks[t]||[]},i.prototype.hasListeners=function(t){return!!this.listeners(t).length}},{}],3:[function(t,e){!function(t,i){"use strict";function s(){if(!n.READY){n.event.determineEventTypes();for(var t in n.gestures)n.gestures.hasOwnProperty(t)&&n.detection.register(n.gestures[t]);n.event.onTouch(n.DOCUMENT,n.EVENT_MOVE,n.detection.detect),n.event.onTouch(n.DOCUMENT,n.EVENT_END,n.detection.detect),n.READY=!0}}var n=function(t,e){return new n.Instance(t,e||{})};n.defaults={stop_browser_behavior:{userSelect:"none",touchAction:"none",touchCallout:"none",contentZooming:"none",userDrag:"none",tapHighlightColor:"rgba(0,0,0,0)"}},n.HAS_POINTEREVENTS=navigator.pointerEnabled||navigator.msPointerEnabled,n.HAS_TOUCHEVENTS="ontouchstart"in t,n.MOBILE_REGEX=/mobile|tablet|ip(ad|hone|od)|android/i,n.NO_MOUSEEVENTS=n.HAS_TOUCHEVENTS&&navigator.userAgent.match(n.MOBILE_REGEX),n.EVENT_TYPES={},n.DIRECTION_DOWN="down",n.DIRECTION_LEFT="left",n.DIRECTION_UP="up",n.DIRECTION_RIGHT="right",n.POINTER_MOUSE="mouse",n.POINTER_TOUCH="touch",n.POINTER_PEN="pen",n.EVENT_START="start",n.EVENT_MOVE="move",n.EVENT_END="end",n.DOCUMENT=document,n.plugins={},n.READY=!1,n.Instance=function(t,e){var i=this; +return s(),this.element=t,this.enabled=!0,this.options=n.utils.extend(n.utils.extend({},n.defaults),e||{}),this.options.stop_browser_behavior&&n.utils.stopDefaultBrowserBehavior(this.element,this.options.stop_browser_behavior),n.event.onTouch(t,n.EVENT_START,function(t){i.enabled&&n.detection.startDetect(i,t)}),this},n.Instance.prototype={on:function(t,e){for(var i=t.split(" "),s=0;s0&&e==n.EVENT_END?e=n.EVENT_MOVE:c||(e=n.EVENT_END),c||null===o?o=h:h=o,i.call(n.detection,s.collectEventData(t,e,h)),n.HAS_POINTEREVENTS&&e==n.EVENT_END&&(c=n.PointerEvent.updatePointer(e,h))),c||(o=null,r=!1,a=!1,n.PointerEvent.reset())}})},determineEventTypes:function(){var t;t=n.HAS_POINTEREVENTS?n.PointerEvent.getEvents():n.NO_MOUSEEVENTS?["touchstart","touchmove","touchend touchcancel"]:["touchstart mousedown","touchmove mousemove","touchend touchcancel mouseup"],n.EVENT_TYPES[n.EVENT_START]=t[0],n.EVENT_TYPES[n.EVENT_MOVE]=t[1],n.EVENT_TYPES[n.EVENT_END]=t[2]},getTouchList:function(t){return n.HAS_POINTEREVENTS?n.PointerEvent.getTouchList():t.touches?t.touches:[{identifier:1,pageX:t.pageX,pageY:t.pageY,target:t.target}]},collectEventData:function(t,e,i){var s=this.getTouchList(i,e),o=n.POINTER_TOUCH;return(i.type.match(/mouse/)||n.PointerEvent.matchType(n.POINTER_MOUSE,i))&&(o=n.POINTER_MOUSE),{center:n.utils.getCenter(s),timeStamp:(new Date).getTime(),target:i.target,touches:s,eventType:e,pointerType:o,srcEvent:i,preventDefault:function(){this.srcEvent.preventManipulation&&this.srcEvent.preventManipulation(),this.srcEvent.preventDefault&&this.srcEvent.preventDefault()},stopPropagation:function(){this.srcEvent.stopPropagation()},stopDetect:function(){return n.detection.stopDetect()}}}},n.PointerEvent={pointers:{},getTouchList:function(){var t=this,e=[];return Object.keys(t.pointers).sort().forEach(function(i){e.push(t.pointers[i])}),e},updatePointer:function(t,e){return t==n.EVENT_END?this.pointers={}:(e.identifier=e.pointerId,this.pointers[e.pointerId]=e),Object.keys(this.pointers).length},matchType:function(t,e){if(!e.pointerType)return!1;var i={};return i[n.POINTER_MOUSE]=e.pointerType==e.MSPOINTER_TYPE_MOUSE||e.pointerType==n.POINTER_MOUSE,i[n.POINTER_TOUCH]=e.pointerType==e.MSPOINTER_TYPE_TOUCH||e.pointerType==n.POINTER_TOUCH,i[n.POINTER_PEN]=e.pointerType==e.MSPOINTER_TYPE_PEN||e.pointerType==n.POINTER_PEN,i[t]},getEvents:function(){return["pointerdown MSPointerDown","pointermove MSPointerMove","pointerup pointercancel MSPointerUp MSPointerCancel"]},reset:function(){this.pointers={}}},n.utils={extend:function(t,e,s){for(var n in e)t[n]!==i&&s||(t[n]=e[n]);return t},hasParent:function(t,e){for(;t;){if(t==e)return!0;t=t.parentNode}return!1},getCenter:function(t){for(var e=[],i=[],s=0,n=t.length;n>s;s++)e.push(t[s].pageX),i.push(t[s].pageY);return{pageX:(Math.min.apply(Math,e)+Math.max.apply(Math,e))/2,pageY:(Math.min.apply(Math,i)+Math.max.apply(Math,i))/2}},getVelocity:function(t,e,i){return{x:Math.abs(e/t)||0,y:Math.abs(i/t)||0}},getAngle:function(t,e){var i=e.pageY-t.pageY,s=e.pageX-t.pageX;return 180*Math.atan2(i,s)/Math.PI},getDirection:function(t,e){var i=Math.abs(t.pageX-e.pageX),s=Math.abs(t.pageY-e.pageY);return i>=s?t.pageX-e.pageX>0?n.DIRECTION_LEFT:n.DIRECTION_RIGHT:t.pageY-e.pageY>0?n.DIRECTION_UP:n.DIRECTION_DOWN},getDistance:function(t,e){var i=e.pageX-t.pageX,s=e.pageY-t.pageY;return Math.sqrt(i*i+s*s)},getScale:function(t,e){return t.length>=2&&e.length>=2?this.getDistance(e[0],e[1])/this.getDistance(t[0],t[1]):1},getRotation:function(t,e){return t.length>=2&&e.length>=2?this.getAngle(e[1],e[0])-this.getAngle(t[1],t[0]):0},isVertical:function(t){return t==n.DIRECTION_UP||t==n.DIRECTION_DOWN},stopDefaultBrowserBehavior:function(t,e){var i,s=["webkit","khtml","moz","ms","o",""];if(e&&t.style){for(var n=0;ni;i++){var o=this.gestures[i];if(!this.stopped&&e[o.name]!==!1&&o.handler.call(o,t,this.current.inst)===!1){this.stopDetect();break}}return this.current&&(this.current.lastEvent=t),t.eventType==n.EVENT_END&&!t.touches.length-1&&this.stopDetect(),t}},stopDetect:function(){this.previous=n.utils.extend({},this.current),this.current=null,this.stopped=!0},extendEventData:function(t){var e=this.current.startEvent;if(e&&(t.touches.length!=e.touches.length||t.touches===e.touches)){e.touches=[];for(var i=0,s=t.touches.length;s>i;i++)e.touches.push(n.utils.extend({},t.touches[i]))}var o=t.timeStamp-e.timeStamp,r=t.center.pageX-e.center.pageX,a=t.center.pageY-e.center.pageY,h=n.utils.getVelocity(o,r,a);return n.utils.extend(t,{deltaTime:o,deltaX:r,deltaY:a,velocityX:h.x,velocityY:h.y,distance:n.utils.getDistance(e.center,t.center),angle:n.utils.getAngle(e.center,t.center),direction:n.utils.getDirection(e.center,t.center),scale:n.utils.getScale(e.touches,t.touches),rotation:n.utils.getRotation(e.touches,t.touches),startEvent:e}),t},register:function(t){var e=t.defaults||{};return e[t.name]===i&&(e[t.name]=!0),n.utils.extend(n.defaults,e,!0),t.index=t.index||1e3,this.gestures.push(t),this.gestures.sort(function(t,e){return t.indexe.index?1:0}),this.gestures}},n.gestures=n.gestures||{},n.gestures.Hold={name:"hold",index:10,defaults:{hold_timeout:500,hold_threshold:1},timer:null,handler:function(t,e){switch(t.eventType){case n.EVENT_START:clearTimeout(this.timer),n.detection.current.name=this.name,this.timer=setTimeout(function(){"hold"==n.detection.current.name&&e.trigger("hold",t)},e.options.hold_timeout);break;case n.EVENT_MOVE:t.distance>e.options.hold_threshold&&clearTimeout(this.timer);break;case n.EVENT_END:clearTimeout(this.timer)}}},n.gestures.Tap={name:"tap",index:100,defaults:{tap_max_touchtime:250,tap_max_distance:10,tap_always:!0,doubletap_distance:20,doubletap_interval:300},handler:function(t,e){if(t.eventType==n.EVENT_END){var i=n.detection.previous,s=!1;if(t.deltaTime>e.options.tap_max_touchtime||t.distance>e.options.tap_max_distance)return;i&&"tap"==i.name&&t.timeStamp-i.lastEvent.timeStamp0&&t.touches.length>e.options.swipe_max_touches)return;(t.velocityX>e.options.swipe_velocity||t.velocityY>e.options.swipe_velocity)&&(e.trigger(this.name,t),e.trigger(this.name+t.direction,t))}}},n.gestures.Drag={name:"drag",index:50,defaults:{drag_min_distance:10,drag_max_touches:1,drag_block_horizontal:!1,drag_block_vertical:!1,drag_lock_to_axis:!1,drag_lock_min_distance:25},triggered:!1,handler:function(t,e){if(n.detection.current.name!=this.name&&this.triggered)return e.trigger(this.name+"end",t),void(this.triggered=!1);if(!(e.options.drag_max_touches>0&&t.touches.length>e.options.drag_max_touches))switch(t.eventType){case n.EVENT_START:this.triggered=!1;break;case n.EVENT_MOVE:if(t.distancee.options.transform_min_rotation&&e.trigger("rotate",t),i>e.options.transform_min_scale&&(e.trigger("pinch",t),e.trigger("pinch"+(t.scale<1?"in":"out"),t));break;case n.EVENT_END:this.triggered&&e.trigger(this.name+"end",t),this.triggered=!1}}},n.gestures.Touch={name:"touch",index:-1/0,defaults:{prevent_default:!1,prevent_mouseevents:!1},handler:function(t,e){return e.options.prevent_mouseevents&&t.pointerType==n.POINTER_MOUSE?void t.stopDetect():(e.options.prevent_default&&t.preventDefault(),void(t.eventType==n.EVENT_START&&e.trigger(this.name,t)))}},n.gestures.Release={name:"release",index:1/0,handler:function(t,e){t.eventType==n.EVENT_END&&e.trigger(this.name,t)}},"object"==typeof e&&"object"==typeof e.exports?e.exports=n:(t.Hammer=n,"function"==typeof t.define&&t.define.amd&&t.define("hammer",[],function(){return n}))}(this)},{}],4:[function(t,e){(function(i){function s(){return{empty:!1,unusedTokens:[],unusedInput:[],overflow:-2,charsLeftOver:0,nullInput:!1,invalidMonth:null,invalidFormat:!1,userInvalidated:!1,iso:!1}}function n(t,e){return function(i){return u(t.call(this,i),e)}}function o(t,e){return function(i){return this.lang().ordinal(t.call(this,i),e)}}function r(){}function a(t){E(t),d(this,t)}function h(t){var e=y(t),i=e.year||0,s=e.month||0,n=e.week||0,o=e.day||0,r=e.hour||0,a=e.minute||0,h=e.second||0,d=e.millisecond||0;this._milliseconds=+d+1e3*h+6e4*a+36e5*r,this._days=+o+7*n,this._months=+s+12*i,this._data={},this._bubble()}function d(t,e){for(var i in e)e.hasOwnProperty(i)&&(t[i]=e[i]);return e.hasOwnProperty("toString")&&(t.toString=e.toString),e.hasOwnProperty("valueOf")&&(t.valueOf=e.valueOf),t}function c(t){var e,i={};for(e in t)t.hasOwnProperty(e)&&ye.hasOwnProperty(e)&&(i[e]=t[e]);return i}function l(t){return 0>t?Math.ceil(t):Math.floor(t)}function u(t,e,i){for(var s=""+Math.abs(t),n=t>=0;s.lengths;s++)(i&&t[s]!==e[s]||!i&&b(t[s])!==b(e[s]))&&r++;return r+o}function v(t){if(t){var e=t.toLowerCase().replace(/(.)s$/,"$1");t=qe[t]||Xe[e]||e}return t}function y(t){var e,i,s={};for(i in t)t.hasOwnProperty(i)&&(e=v(i),e&&(s[e]=t[i]));return s}function _(t){var e,s;if(0===t.indexOf("week"))e=7,s="day";else{if(0!==t.indexOf("month"))return;e=12,s="month"}oe[t]=function(n,o){var r,a,h=oe.fn._lang[t],d=[];if("number"==typeof n&&(o=n,n=i),a=function(t){var e=oe().utc().set(s,t);return h.call(oe.fn._lang,e,n||"")},null!=o)return a(o);for(r=0;e>r;r++)d.push(a(r));return d}}function b(t){var e=+t,i=0;return 0!==e&&isFinite(e)&&(i=e>=0?Math.floor(e):Math.ceil(e)),i}function w(t,e){return new Date(Date.UTC(t,e+1,0)).getUTCDate()}function S(t){return x(t)?366:365}function x(t){return t%4===0&&t%100!==0||t%400===0}function E(t){var e;t._a&&-2===t._pf.overflow&&(e=t._a[le]<0||t._a[le]>11?le:t._a[ue]<1||t._a[ue]>w(t._a[ce],t._a[le])?ue:t._a[pe]<0||t._a[pe]>23?pe:t._a[ge]<0||t._a[ge]>59?ge:t._a[fe]<0||t._a[fe]>59?fe:t._a[me]<0||t._a[me]>999?me:-1,t._pf._overflowDayOfYear&&(ce>e||e>ue)&&(e=ue),t._pf.overflow=e)}function T(t){return null==t._isValid&&(t._isValid=!isNaN(t._d.getTime())&&t._pf.overflow<0&&!t._pf.empty&&!t._pf.invalidMonth&&!t._pf.nullInput&&!t._pf.invalidFormat&&!t._pf.userInvalidated,t._strict&&(t._isValid=t._isValid&&0===t._pf.charsLeftOver&&0===t._pf.unusedTokens.length)),t._isValid}function D(t){return t?t.toLowerCase().replace("_","-"):t}function C(t,e){return e._isUTC?oe(t).zone(e._offset||0):oe(t).local()}function M(t,e){return e.abbr=t,ve[t]||(ve[t]=new r),ve[t].set(e),ve[t]}function I(t){delete ve[t]}function N(e){var i,s,n,o,r=0,a=function(e){if(!ve[e]&&_e)try{t("./lang/"+e)}catch(i){}return ve[e]};if(!e)return oe.fn._lang;if(!g(e)){if(s=a(e))return s;e=[e]}for(;r0;){if(s=a(o.slice(0,i).join("-")))return s;if(n&&n.length>=i&&m(o,n,!0)>=i-1)break;i--}r++}return oe.fn._lang}function O(t){return t.match(/\[[\s\S]/)?t.replace(/^\[|\]$/g,""):t.replace(/\\/g,"")}function L(t){var e,i,s=t.match(xe);for(e=0,i=s.length;i>e;e++)s[e]=Qe[s[e]]?Qe[s[e]]:O(s[e]);return function(n){var o="";for(e=0;i>e;e++)o+=s[e]instanceof Function?s[e].call(n,t):s[e];return o}}function k(t,e){return t.isValid()?(e=P(e,t.lang()),Ze[e]||(Ze[e]=L(e)),Ze[e](t)):t.lang().invalidDate()}function P(t,e){function i(t){return e.longDateFormat(t)||t}var s=5;for(Ee.lastIndex=0;s>=0&&Ee.test(t);)t=t.replace(Ee,i),Ee.lastIndex=0,s-=1;return t}function A(t,e){var i,s=e._strict;switch(t){case"DDDD":return ze;case"YYYY":case"GGGG":case"gggg":return s?Re:Ce;case"Y":case"G":case"g":return He;case"YYYYYY":case"YYYYY":case"GGGGG":case"ggggg":return s?Fe:Me;case"S":if(s)return Pe;case"SS":if(s)return Ae;case"SSS":if(s)return ze;case"DDD":return De;case"MMM":case"MMMM":case"dd":case"ddd":case"dddd":return Ne;case"a":case"A":return N(e._l)._meridiemParse;case"X":return ke;case"Z":case"ZZ":return Oe;case"T":return Le;case"SSSS":return Ie;case"MM":case"DD":case"YY":case"GG":case"gg":case"HH":case"hh":case"mm":case"ss":case"ww":case"WW":return s?Ae:Te;case"M":case"D":case"d":case"H":case"h":case"m":case"s":case"w":case"W":case"e":case"E":return Te;default:return i=new RegExp(B(W(t.replace("\\","")),"i"))}}function z(t){t=t||"";var e=t.match(Oe)||[],i=e[e.length-1]||[],s=(i+"").match(je)||["-",0,0],n=+(60*s[1])+b(s[2]);return"+"===s[0]?-n:n}function R(t,e,i){var s,n=i._a;switch(t){case"M":case"MM":null!=e&&(n[le]=b(e)-1);break;case"MMM":case"MMMM":s=N(i._l).monthsParse(e),null!=s?n[le]=s:i._pf.invalidMonth=e;break;case"D":case"DD":null!=e&&(n[ue]=b(e));break;case"DDD":case"DDDD":null!=e&&(i._dayOfYear=b(e));break;case"YY":n[ce]=b(e)+(b(e)>68?1900:2e3);break;case"YYYY":case"YYYYY":case"YYYYYY":n[ce]=b(e);break;case"a":case"A":i._isPm=N(i._l).isPM(e);break;case"H":case"HH":case"h":case"hh":n[pe]=b(e);break;case"m":case"mm":n[ge]=b(e);break;case"s":case"ss":n[fe]=b(e);break;case"S":case"SS":case"SSS":case"SSSS":n[me]=b(1e3*("0."+e));break;case"X":i._d=new Date(1e3*parseFloat(e));break;case"Z":case"ZZ":i._useUTC=!0,i._tzm=z(e);break;case"w":case"ww":case"W":case"WW":case"d":case"dd":case"ddd":case"dddd":case"e":case"E":t=t.substr(0,1);case"gg":case"gggg":case"GG":case"GGGG":case"GGGGG":t=t.substr(0,2),e&&(i._w=i._w||{},i._w[t]=e)}}function F(t){var e,i,s,n,o,r,a,h,d,c,l=[];if(!t._d){for(s=Y(t),t._w&&null==t._a[ue]&&null==t._a[le]&&(o=function(e){var i=parseInt(e,10);return e?e.length<3?i>68?1900+i:2e3+i:i:null==t._a[ce]?oe().weekYear():t._a[ce]},r=t._w,null!=r.GG||null!=r.W||null!=r.E?a=J(o(r.GG),r.W||1,r.E,4,1):(h=N(t._l),d=null!=r.d?Z(r.d,h):null!=r.e?parseInt(r.e,10)+h._week.dow:0,c=parseInt(r.w,10)||1,null!=r.d&&dS(n)&&(t._pf._overflowDayOfYear=!0),i=X(n,0,t._dayOfYear),t._a[le]=i.getUTCMonth(),t._a[ue]=i.getUTCDate()),e=0;3>e&&null==t._a[e];++e)t._a[e]=l[e]=s[e];for(;7>e;e++)t._a[e]=l[e]=null==t._a[e]?2===e?1:0:t._a[e];l[pe]+=b((t._tzm||0)/60),l[ge]+=b((t._tzm||0)%60),t._d=(t._useUTC?X:q).apply(null,l)}}function H(t){var e;t._d||(e=y(t._i),t._a=[e.year,e.month,e.day,e.hour,e.minute,e.second,e.millisecond],F(t))}function Y(t){var e=new Date;return t._useUTC?[e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate()]:[e.getFullYear(),e.getMonth(),e.getDate()]}function G(t){t._a=[],t._pf.empty=!0;var e,i,s,n,o,r=N(t._l),a=""+t._i,h=a.length,d=0;for(s=P(t._f,r).match(xe)||[],e=0;e0&&t._pf.unusedInput.push(o),a=a.slice(a.indexOf(i)+i.length),d+=i.length),Qe[n]?(i?t._pf.empty=!1:t._pf.unusedTokens.push(n),R(n,i,t)):t._strict&&!i&&t._pf.unusedTokens.push(n);t._pf.charsLeftOver=h-d,a.length>0&&t._pf.unusedInput.push(a),t._isPm&&t._a[pe]<12&&(t._a[pe]+=12),t._isPm===!1&&12===t._a[pe]&&(t._a[pe]=0),F(t),E(t)}function W(t){return t.replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(t,e,i,s,n){return e||i||s||n})}function B(t){return t.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function j(t){var e,i,n,o,r;if(0===t._f.length)return t._pf.invalidFormat=!0,void(t._d=new Date(0/0));for(o=0;or)&&(n=r,i=e));d(t,i||e)}function V(t){var e,i,s=t._i,n=Ye.exec(s);if(n){for(t._pf.iso=!0,e=0,i=We.length;i>e;e++)if(We[e][1].exec(s)){t._f=We[e][0]+(n[6]||" ");break}for(e=0,i=Be.length;i>e;e++)if(Be[e][1].exec(s)){t._f+=Be[e][0];break}s.match(Oe)&&(t._f+="Z"),G(t)}else t._d=new Date(s)}function U(t){var e=t._i,s=be.exec(e);e===i?t._d=new Date:s?t._d=new Date(+s[1]):"string"==typeof e?V(t):g(e)?(t._a=e.slice(0),F(t)):f(e)?t._d=new Date(+e):"object"==typeof e?H(t):t._d=new Date(e)}function q(t,e,i,s,n,o,r){var a=new Date(t,e,i,s,n,o,r);return 1970>t&&a.setFullYear(t),a}function X(t){var e=new Date(Date.UTC.apply(null,arguments));return 1970>t&&e.setUTCFullYear(t),e}function Z(t,e){if("string"==typeof t)if(isNaN(t)){if(t=e.weekdaysParse(t),"number"!=typeof t)return null}else t=parseInt(t,10);return t}function K(t,e,i,s,n){return n.relativeTime(e||1,!!i,t,s)}function $(t,e,i){var s=de(Math.abs(t)/1e3),n=de(s/60),o=de(n/60),r=de(o/24),a=de(r/365),h=45>s&&["s",s]||1===n&&["m"]||45>n&&["mm",n]||1===o&&["h"]||22>o&&["hh",o]||1===r&&["d"]||25>=r&&["dd",r]||45>=r&&["M"]||345>r&&["MM",de(r/30)]||1===a&&["y"]||["yy",a];return h[2]=e,h[3]=t>0,h[4]=i,K.apply({},h)}function Q(t,e,i){var s,n=i-e,o=i-t.day();return o>n&&(o-=7),n-7>o&&(o+=7),s=oe(t).add("d",o),{week:Math.ceil(s.dayOfYear()/7),year:s.year()}}function J(t,e,i,s,n){var o,r,a=X(t,0,1).getUTCDay();return i=null!=i?i:n,o=n-a+(a>s?7:0)-(n>a?7:0),r=7*(e-1)+(i-n)+o+1,{year:r>0?t:t-1,dayOfYear:r>0?r:S(t-1)+r}}function te(t){var e=t._i,i=t._f;return null===e?oe.invalid({nullInput:!0}):("string"==typeof e&&(t._i=e=N().preparse(e)),oe.isMoment(e)?(t=c(e),t._d=new Date(+e._d)):i?g(i)?j(t):G(t):U(t),new a(t))}function ee(t,e){oe.fn[t]=oe.fn[t+"s"]=function(t){var i=this._isUTC?"UTC":"";return null!=t?(this._d["set"+i+e](t),oe.updateOffset(this),this):this._d["get"+i+e]()}}function ie(t){oe.duration.fn[t]=function(){return this._data[t]}}function se(t,e){oe.duration.fn["as"+t]=function(){return+this/e}}function ne(t){var e=!1,i=oe;"undefined"==typeof ender&&(t?(he.moment=function(){return!e&&console&&console.warn&&(e=!0,console.warn("Accessing Moment through the global scope is deprecated, and will be removed in an upcoming release.")),i.apply(null,arguments)},d(he.moment,i)):he.moment=oe)}for(var oe,re,ae="2.5.1",he=this,de=Math.round,ce=0,le=1,ue=2,pe=3,ge=4,fe=5,me=6,ve={},ye={_isAMomentObject:null,_i:null,_f:null,_l:null,_strict:null,_isUTC:null,_offset:null,_pf:null,_lang:null},_e="undefined"!=typeof e&&e.exports&&"undefined"!=typeof t,be=/^\/?Date\((\-?\d+)/i,we=/(\-)?(?:(\d*)\.)?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?)?/,Se=/^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/,xe=/(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,4}|X|zz?|ZZ?|.)/g,Ee=/(\[[^\[]*\])|(\\)?(LT|LL?L?L?|l{1,4})/g,Te=/\d\d?/,De=/\d{1,3}/,Ce=/\d{1,4}/,Me=/[+\-]?\d{1,6}/,Ie=/\d+/,Ne=/[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i,Oe=/Z|[\+\-]\d\d:?\d\d/gi,Le=/T/i,ke=/[\+\-]?\d+(\.\d{1,3})?/,Pe=/\d/,Ae=/\d\d/,ze=/\d{3}/,Re=/\d{4}/,Fe=/[+-]?\d{6}/,He=/[+-]?\d+/,Ye=/^\s*(?:[+-]\d{6}|\d{4})-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,Ge="YYYY-MM-DDTHH:mm:ssZ",We=[["YYYYYY-MM-DD",/[+-]\d{6}-\d{2}-\d{2}/],["YYYY-MM-DD",/\d{4}-\d{2}-\d{2}/],["GGGG-[W]WW-E",/\d{4}-W\d{2}-\d/],["GGGG-[W]WW",/\d{4}-W\d{2}/],["YYYY-DDD",/\d{4}-\d{3}/]],Be=[["HH:mm:ss.SSSS",/(T| )\d\d:\d\d:\d\d\.\d{1,3}/],["HH:mm:ss",/(T| )\d\d:\d\d:\d\d/],["HH:mm",/(T| )\d\d:\d\d/],["HH",/(T| )\d\d/]],je=/([\+\-]|\d\d)/gi,Ve="Date|Hours|Minutes|Seconds|Milliseconds".split("|"),Ue={Milliseconds:1,Seconds:1e3,Minutes:6e4,Hours:36e5,Days:864e5,Months:2592e6,Years:31536e6},qe={ms:"millisecond",s:"second",m:"minute",h:"hour",d:"day",D:"date",w:"week",W:"isoWeek",M:"month",y:"year",DDD:"dayOfYear",e:"weekday",E:"isoWeekday",gg:"weekYear",GG:"isoWeekYear"},Xe={dayofyear:"dayOfYear",isoweekday:"isoWeekday",isoweek:"isoWeek",weekyear:"weekYear",isoweekyear:"isoWeekYear"},Ze={},Ke="DDD w W M D d".split(" "),$e="M D H h m s w W".split(" "),Qe={M:function(){return this.month()+1},MMM:function(t){return this.lang().monthsShort(this,t)},MMMM:function(t){return this.lang().months(this,t)},D:function(){return this.date()},DDD:function(){return this.dayOfYear()},d:function(){return this.day()},dd:function(t){return this.lang().weekdaysMin(this,t)},ddd:function(t){return this.lang().weekdaysShort(this,t)},dddd:function(t){return this.lang().weekdays(this,t)},w:function(){return this.week()},W:function(){return this.isoWeek()},YY:function(){return u(this.year()%100,2)},YYYY:function(){return u(this.year(),4)},YYYYY:function(){return u(this.year(),5)},YYYYYY:function(){var t=this.year(),e=t>=0?"+":"-";return e+u(Math.abs(t),6)},gg:function(){return u(this.weekYear()%100,2)},gggg:function(){return u(this.weekYear(),4)},ggggg:function(){return u(this.weekYear(),5)},GG:function(){return u(this.isoWeekYear()%100,2)},GGGG:function(){return u(this.isoWeekYear(),4)},GGGGG:function(){return u(this.isoWeekYear(),5)},e:function(){return this.weekday()},E:function(){return this.isoWeekday()},a:function(){return this.lang().meridiem(this.hours(),this.minutes(),!0)},A:function(){return this.lang().meridiem(this.hours(),this.minutes(),!1)},H:function(){return this.hours()},h:function(){return this.hours()%12||12},m:function(){return this.minutes()},s:function(){return this.seconds()},S:function(){return b(this.milliseconds()/100)},SS:function(){return u(b(this.milliseconds()/10),2)},SSS:function(){return u(this.milliseconds(),3)},SSSS:function(){return u(this.milliseconds(),3)},Z:function(){var t=-this.zone(),e="+";return 0>t&&(t=-t,e="-"),e+u(b(t/60),2)+":"+u(b(t)%60,2)},ZZ:function(){var t=-this.zone(),e="+";return 0>t&&(t=-t,e="-"),e+u(b(t/60),2)+u(b(t)%60,2)},z:function(){return this.zoneAbbr()},zz:function(){return this.zoneName()},X:function(){return this.unix()},Q:function(){return this.quarter()}},Je=["months","monthsShort","weekdays","weekdaysShort","weekdaysMin"];Ke.length;)re=Ke.pop(),Qe[re+"o"]=o(Qe[re],re);for(;$e.length;)re=$e.pop(),Qe[re+re]=n(Qe[re],2);for(Qe.DDDD=n(Qe.DDD,3),d(r.prototype,{set:function(t){var e,i;for(i in t)e=t[i],"function"==typeof e?this[i]=e:this["_"+i]=e},_months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),months:function(t){return this._months[t.month()]},_monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),monthsShort:function(t){return this._monthsShort[t.month()]},monthsParse:function(t){var e,i,s;for(this._monthsParse||(this._monthsParse=[]),e=0;12>e;e++)if(this._monthsParse[e]||(i=oe.utc([2e3,e]),s="^"+this.months(i,"")+"|^"+this.monthsShort(i,""),this._monthsParse[e]=new RegExp(s.replace(".",""),"i")),this._monthsParse[e].test(t))return e},_weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdays:function(t){return this._weekdays[t.day()]},_weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysShort:function(t){return this._weekdaysShort[t.day()]},_weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),weekdaysMin:function(t){return this._weekdaysMin[t.day()]},weekdaysParse:function(t){var e,i,s;for(this._weekdaysParse||(this._weekdaysParse=[]),e=0;7>e;e++)if(this._weekdaysParse[e]||(i=oe([2e3,1]).day(e),s="^"+this.weekdays(i,"")+"|^"+this.weekdaysShort(i,"")+"|^"+this.weekdaysMin(i,""),this._weekdaysParse[e]=new RegExp(s.replace(".",""),"i")),this._weekdaysParse[e].test(t))return e},_longDateFormat:{LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D YYYY",LLL:"MMMM D YYYY LT",LLLL:"dddd, MMMM D YYYY LT"},longDateFormat:function(t){var e=this._longDateFormat[t];return!e&&this._longDateFormat[t.toUpperCase()]&&(e=this._longDateFormat[t.toUpperCase()].replace(/MMMM|MM|DD|dddd/g,function(t){return t.slice(1)}),this._longDateFormat[t]=e),e},isPM:function(t){return"p"===(t+"").toLowerCase().charAt(0)},_meridiemParse:/[ap]\.?m?\.?/i,meridiem:function(t,e,i){return t>11?i?"pm":"PM":i?"am":"AM"},_calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},calendar:function(t,e){var i=this._calendar[t];return"function"==typeof i?i.apply(e):i},_relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},relativeTime:function(t,e,i,s){var n=this._relativeTime[i];return"function"==typeof n?n(t,e,i,s):n.replace(/%d/i,t)},pastFuture:function(t,e){var i=this._relativeTime[t>0?"future":"past"];return"function"==typeof i?i(e):i.replace(/%s/i,e)},ordinal:function(t){return this._ordinal.replace("%d",t)},_ordinal:"%d",preparse:function(t){return t},postformat:function(t){return t},week:function(t){return Q(t,this._week.dow,this._week.doy).week},_week:{dow:0,doy:6},_invalidDate:"Invalid date",invalidDate:function(){return this._invalidDate}}),oe=function(t,e,n,o){var r;return"boolean"==typeof n&&(o=n,n=i),r={},r._isAMomentObject=!0,r._i=t,r._f=e,r._l=n,r._strict=o,r._isUTC=!1,r._pf=s(),te(r)},oe.utc=function(t,e,n,o){var r;return"boolean"==typeof n&&(o=n,n=i),r={},r._isAMomentObject=!0,r._useUTC=!0,r._isUTC=!0,r._l=n,r._i=t,r._f=e,r._strict=o,r._pf=s(),te(r).utc()},oe.unix=function(t){return oe(1e3*t)},oe.duration=function(t,e){var i,s,n,o=t,r=null;return oe.isDuration(t)?o={ms:t._milliseconds,d:t._days,M:t._months}:"number"==typeof t?(o={},e?o[e]=t:o.milliseconds=t):(r=we.exec(t))?(i="-"===r[1]?-1:1,o={y:0,d:b(r[ue])*i,h:b(r[pe])*i,m:b(r[ge])*i,s:b(r[fe])*i,ms:b(r[me])*i}):(r=Se.exec(t))&&(i="-"===r[1]?-1:1,n=function(t){var e=t&&parseFloat(t.replace(",","."));return(isNaN(e)?0:e)*i},o={y:n(r[2]),M:n(r[3]),d:n(r[4]),h:n(r[5]),m:n(r[6]),s:n(r[7]),w:n(r[8])}),s=new h(o),oe.isDuration(t)&&t.hasOwnProperty("_lang")&&(s._lang=t._lang),s},oe.version=ae,oe.defaultFormat=Ge,oe.updateOffset=function(){},oe.lang=function(t,e){var i;return t?(e?M(D(t),e):null===e?(I(t),t="en"):ve[t]||N(t),i=oe.duration.fn._lang=oe.fn._lang=N(t),i._abbr):oe.fn._lang._abbr},oe.langData=function(t){return t&&t._lang&&t._lang._abbr&&(t=t._lang._abbr),N(t)},oe.isMoment=function(t){return t instanceof a||null!=t&&t.hasOwnProperty("_isAMomentObject")},oe.isDuration=function(t){return t instanceof h},re=Je.length-1;re>=0;--re)_(Je[re]);for(oe.normalizeUnits=function(t){return v(t)},oe.invalid=function(t){var e=oe.utc(0/0);return null!=t?d(e._pf,t):e._pf.userInvalidated=!0,e},oe.parseZone=function(t){return oe(t).parseZone()},d(oe.fn=a.prototype,{clone:function(){return oe(this)},valueOf:function(){return+this._d+6e4*(this._offset||0)},unix:function(){return Math.floor(+this/1e3)},toString:function(){return this.clone().lang("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")},toDate:function(){return this._offset?new Date(+this):this._d},toISOString:function(){var t=oe(this).utc();return 00:!1},parsingFlags:function(){return d({},this._pf)},invalidAt:function(){return this._pf.overflow},utc:function(){return this.zone(0)},local:function(){return this.zone(0),this._isUTC=!1,this},format:function(t){var e=k(this,t||oe.defaultFormat);return this.lang().postformat(e)},add:function(t,e){var i;return i="string"==typeof t?oe.duration(+e,t):oe.duration(t,e),p(this,i,1),this},subtract:function(t,e){var i;return i="string"==typeof t?oe.duration(+e,t):oe.duration(t,e),p(this,i,-1),this},diff:function(t,e,i){var s,n,o=C(t,this),r=6e4*(this.zone()-o.zone());return e=v(e),"year"===e||"month"===e?(s=432e5*(this.daysInMonth()+o.daysInMonth()),n=12*(this.year()-o.year())+(this.month()-o.month()),n+=(this-oe(this).startOf("month")-(o-oe(o).startOf("month")))/s,n-=6e4*(this.zone()-oe(this).startOf("month").zone()-(o.zone()-oe(o).startOf("month").zone()))/s,"year"===e&&(n/=12)):(s=this-o,n="second"===e?s/1e3:"minute"===e?s/6e4:"hour"===e?s/36e5:"day"===e?(s-r)/864e5:"week"===e?(s-r)/6048e5:s),i?n:l(n)},from:function(t,e){return oe.duration(this.diff(t)).lang(this.lang()._abbr).humanize(!e)},fromNow:function(t){return this.from(oe(),t)},calendar:function(){var t=C(oe(),this).startOf("day"),e=this.diff(t,"days",!0),i=-6>e?"sameElse":-1>e?"lastWeek":0>e?"lastDay":1>e?"sameDay":2>e?"nextDay":7>e?"nextWeek":"sameElse";return this.format(this.lang().calendar(i,this))},isLeapYear:function(){return x(this.year())},isDST:function(){return this.zone()+oe(t).startOf(e)},isBefore:function(t,e){return e="undefined"!=typeof e?e:"millisecond",+this.clone().startOf(e)<+oe(t).startOf(e)},isSame:function(t,e){return e=e||"ms",+this.clone().startOf(e)===+C(t,this).startOf(e)},min:function(t){return t=oe.apply(null,arguments),this>t?this:t},max:function(t){return t=oe.apply(null,arguments),t>this?this:t},zone:function(t){var e=this._offset||0;return null==t?this._isUTC?e:this._d.getTimezoneOffset():("string"==typeof t&&(t=z(t)),Math.abs(t)<16&&(t=60*t),this._offset=t,this._isUTC=!0,e!==t&&p(this,oe.duration(e-t,"m"),1,!0),this)},zoneAbbr:function(){return this._isUTC?"UTC":""},zoneName:function(){return this._isUTC?"Coordinated Universal Time":""},parseZone:function(){return this._tzm?this.zone(this._tzm):"string"==typeof this._i&&this.zone(this._i),this},hasAlignedHourOffset:function(t){return t=t?oe(t).zone():0,(this.zone()-t)%60===0},daysInMonth:function(){return w(this.year(),this.month())},dayOfYear:function(t){var e=de((oe(this).startOf("day")-oe(this).startOf("year"))/864e5)+1;return null==t?e:this.add("d",t-e)},quarter:function(){return Math.ceil((this.month()+1)/3)},weekYear:function(t){var e=Q(this,this.lang()._week.dow,this.lang()._week.doy).year;return null==t?e:this.add("y",t-e)},isoWeekYear:function(t){var e=Q(this,1,4).year;return null==t?e:this.add("y",t-e)},week:function(t){var e=this.lang().week(this);return null==t?e:this.add("d",7*(t-e))},isoWeek:function(t){var e=Q(this,1,4).week;return null==t?e:this.add("d",7*(t-e))},weekday:function(t){var e=(this.day()+7-this.lang()._week.dow)%7;return null==t?e:this.add("d",t-e)},isoWeekday:function(t){return null==t?this.day()||7:this.day(this.day()%7?t:t-7)},get:function(t){return t=v(t),this[t]()},set:function(t,e){return t=v(t),"function"==typeof this[t]&&this[t](e),this},lang:function(t){return t===i?this._lang:(this._lang=N(t),this)}}),re=0;re-1?!1:"INPUT"==i||"SELECT"==i||"TEXTAREA"==i||e.contentEditable&&"true"==e.contentEditable}function o(t,e){return t.sort().join(",")===e.sort().join(",")}function r(t){t=t||{};var e,i=!1;for(e in C)t[e]?i=!0:C[e]=0;i||(I=!1)}function a(t,e,i,s,n){var r,a,h=[];if(!T[t])return[];for("keyup"==i&&u(t)&&(e=[t]),r=0;r95&&112>t||w.hasOwnProperty(t)&&(_[w[t]]=t)}return _}function f(t,e,i){return i||(i=g()[t]?"keydown":"keypress"),"keypress"==i&&e.length&&(i="keydown"),i}function m(t,e,i,n){C[t]=0,n||(n=f(e[0],[]));var o,a=function(){I=n,++C[t],p()},h=function(t){d(i,t),"keyup"!==n&&(M=s(t)),setTimeout(r,10)};for(o=0;o1)return m(t,d,e,i);for(h="+"===t?["+"]:t.split("+"),o=0;o":".","?":"/","|":"\\"},E={option:"alt",command:"meta","return":"enter",escape:"esc"},T={},D={},C={},M=!1,I=!1,N=1;20>N;++N)w[111+N]="f"+N;for(N=0;9>=N;++N)w[N+96]=N;i(document,"keypress",l),i(document,"keydown",l),i(document,"keyup",l);var O={bind:function(t,e,i){return y(t instanceof Array?t:[t],e,i),D[t+":"+i]=e,this},unbind:function(t,e){return D[t+":"+e]&&(delete D[t+":"+e],this.bind(t,function(){},e)),this},trigger:function(t,e){return D[t+":"+e](),this},reset:function(){return T={},D={},this}};e.exports=O},{}]},{},[1])(1)}); \ No newline at end of file diff --git a/web/examples/algorithms.html b/web/examples/algorithms.html new file mode 100644 index 0000000..bccc80b --- /dev/null +++ b/web/examples/algorithms.html @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + +
+ Looks ugly? Hit !
+ + + diff --git a/web/examples/algorithms.js b/web/examples/algorithms.js new file mode 100644 index 0000000..9ac1c6f --- /dev/null +++ b/web/examples/algorithms.js @@ -0,0 +1,102 @@ +var redraw; + +window.onload = function() { + var width = $(document).width(); + var height = $(document).height() - 100; + + /* Showcase of the Bellman-Ford search algorithm finding shortest paths + from one point to every node */ + + /* */ + + /* We need to write a new node renderer function to display the computed + distance. + (the Raphael graph drawing implementation of Dracula can draw this shape, + please consult the RaphaelJS reference for details http://raphaeljs.com/) */ + var render = function(r, n) { + /* the Raphael set is obligatory, containing all you want to display */ + var set = r.set().push( + /* custom objects go here */ + r.rect(n.point[0]-30, n.point[1]-13, 60, 44).attr({"fill": "#feb", r : "12px", "stroke-width" : n.distance == 0 ? "3px" : "1px" })).push( + r.text(n.point[0], n.point[1] + 10, (n.label || n.id) + "\n(" + (n.distance == undefined ? "Infinity" : n.distance) + ")")); + return set; + }; + + var g = new Graph(); + + /* modify the edge creation to attach random weights */ + g.edgeFactory.build = function(source, target) { + var e = jQuery.extend(true, {}, this.template); + e.source = source; + e.target = target; + e.style.label = e.weight = Math.floor(Math.random() * 10) + 1; + return e; + } + + /* creating nodes and passing the new renderer function to overwrite the default one */ + g.addNode("New York", {render:render}); // TODO add currying support for nicer code + g.addNode("Berlin", {render:render}); + g.addNode("Tel Aviv", {render:render}); + g.addNode("Tokyo", {render:render}); + g.addNode("Roma", {render:render}); + g.addNode("Madrid", {render:render}); + + /* connections */ + g.addEdge("Tokyo", "Tel Aviv"/*, {weight:9, directed: true, stroke : "#bfa"}*/); // also supports directed graphs, but currently doesn't look that nice + g.addEdge("Tokyo", "New York"); + g.addEdge("Tokyo", "Berlin"); + g.addEdge("Tel Aviv", "Berlin"); + g.addEdge("Tel Aviv", "New York"); + g.addEdge("Tel Aviv", "Roma"); + g.addEdge("Roma", "New York"); + g.addEdge("Berlin", "New York"); + g.addEdge("Madrid", "New York"); + g.addEdge("Madrid", "Roma"); + g.addEdge("Madrid", "Tokyo"); + + /* random edge weights (our undirected graph is modelled as a bidirectional graph) */ +/* for(e in g.edges) + if(g.edges[e].backedge != undefined) { + g.edges[e].weight = Math.floor(Math.random()*10) + 1; + g.edges[e].backedge.weight = g.edges[e].weight; + } +*/ + /* layout the graph using the Spring layout implementation */ + var layouter = new Graph.Layout.Spring(g); + + /* draw the graph using the RaphaelJS draw implementation */ + + /* calculating the shortest paths via Bellman Ford */ +// bellman_ford(g, g.nodes["Berlin"]); + + /* calculating the shortest paths via Dijkstra */ + dijkstra(g, g.nodes["Berlin"]); + + /* calculating the shortest paths via Floyd-Warshall */ + floyd_warshall(g, g.nodes["Berlin"]); + + + /* colourising the shortest paths and setting labels */ + for(e in g.edges) { + if(g.edges[e].target.predecessor === g.edges[e].source || g.edges[e].source.predecessor === g.edges[e].target) { + g.edges[e].style.stroke = "#bfa"; + g.edges[e].style.fill = "#56f"; + } else { + g.edges[e].style.stroke = "#aaa"; + } + } + + var renderer = new Graph.Renderer.Raphael('canvas', g, width, height); + + redraw = function() { + layouter.layout(); + renderer.draw(); + }; + +/* var pos=0; + step = function(dir) { + pos+=dir; + var renderer = new Graph.Renderer.Raphael('canvas', g.snapshots[pos], width, height); + renderer.draw(); + };*/ +}; \ No newline at end of file diff --git a/web/examples/classes.html b/web/examples/classes.html new file mode 100644 index 0000000..6a5e82b --- /dev/null +++ b/web/examples/classes.html @@ -0,0 +1,18 @@ + + + + + + + + + + + +
+ + diff --git a/web/examples/classes.js b/web/examples/classes.js new file mode 100644 index 0000000..c909af3 --- /dev/null +++ b/web/examples/classes.js @@ -0,0 +1,23 @@ +//Show UCLA CS class dependencies (not complete) +$(document).ready(function() { + var width = $(document).width(); + var height = $(document).height(); + var g = new Graph(); + g.edgeFactory.template.style.directed = true; + g.addEdge("CS 31","CS 32"); + g.addEdge("CS 32","CS 33"); + g.addEdge("CS 33","CS 35L"); + g.addNode("CS M51A"); + g.addEdge("CS 32", "CS 111"); + g.addEdge("CS 33", "CS 111"); + g.addEdge("CS 35L", "CS 111"); + g.addEdge("CS 32", "CS 118"); + g.addEdge("CS 33", "CS 118"); + g.addEdge("CS 35L", "CS 118"); + g.addEdge("CS 111", "CS 118"); + g.addEdge("CS 32", "CS 131"); + g.addEdge("CS 33", "CS 131"); + g.addEdge("CS 35L", "CS 131"); + var layouter = new Graph.Layout.Ordered(g, topological_sort(g)); + var renderer = new Graph.Renderer.Raphael('canvas', g, width, height); +}); \ No newline at end of file diff --git a/web/examples/example.html b/web/examples/example.html new file mode 100644 index 0000000..a039e8d --- /dev/null +++ b/web/examples/example.html @@ -0,0 +1,15 @@ + + + + + + + + + +
+ + + + + diff --git a/web/examples/example.js b/web/examples/example.js new file mode 100644 index 0000000..f659623 --- /dev/null +++ b/web/examples/example.js @@ -0,0 +1,115 @@ + +var redraw, g, renderer; + +/* only do all this when document has finished loading (needed for RaphaelJS) */ +window.onload = function() { + + var width = $(document).width() - 20; + var height = $(document).height() - 60; + + g = new Graph(); + + /* add a simple node */ + g.addNode("strawberry"); + g.addNode("cherry"); + + /* add a node with a customized label */ + g.addNode("1", { label : "Tomato" }); + + + /* add a node with a customized shape + (the Raphael graph drawing implementation can draw this shape, please + consult the RaphaelJS reference for details http://raphaeljs.com/) */ +// var render = function(r, n) { +// var label = r.text(0, 30, n.label).attr({opacity:0}); + /* the Raphael set is obligatory, containing all you want to display */ +// var set = r.set().push( +// r.rect(-30, -13, 62, 86).attr({"fill": "#fa8", "stroke-width": 2, r : "9px"})) +// .push(label); + /* make the label show only on hover */ +// set.hover(function(){ label.animate({opacity:1,"fill-opacity":1}, 500); }, function(){ label.animate({opacity:0},300); }); + +// tooltip = r.set() +// .push( +// r.rect(0, 0, 90, 30).attr({"fill": "#fec", "stroke-width": 1, r : "9px"}) +// ).push( +// r.text(25, 15, "overlay").attr({"fill": "#000000"}) +// ); +// for(i in set.items) { +// set.items[i].tooltip(tooltip); +// }; +// // set.tooltip(r.set().push(r.rect(0, 0, 30, 30).attr({"fill": "#fec", "stroke-width": 1, r : "9px"})).hide()); +// return set; +// }; + + g.addNode("id35", { + label : "meat\nand\ngreed" //, + /* filling the shape with a color makes it easier to be dragged */ + /* arguments: r = Raphael object, n : node object */ +// render : render + }); + // g.addNode("Wheat", { + /* filling the shape with a color makes it easier to be dragged */ + /* arguments: r = Raphael object, n : node object */ + // shapes : [ { + // type: "rect", + // x: 10, + // y: 10, + // width: 25, + // height: 25, + // stroke: "#f00" + // }, { + // type: "text", + // x: 30, + // y: 40, + // text: "Dump" + // }], + // overlay : "Hello World!" + // }); + + st = { directed: true, label : "Label", + "label-style" : { + "font-size": 20 + } + }; + g.addEdge("kiwi", "penguin", st); + + /* connect nodes with edges */ + g.addEdge("strawberry", "cherry"); + g.addEdge("cherry", "apple"); + g.addEdge("cherry", "apple") + g.addEdge("1", "id35"); + g.addEdge("penguin", "id35"); + g.addEdge("penguin", "apple"); + g.addEdge("kiwi", "id35"); + + /* a directed connection, using an arrow */ + g.addEdge("1", "cherry", { directed : true } ); + + /* customize the colors of that edge */ + g.addEdge("id35", "apple", { stroke : "#bfa" , fill : "#56f", label : "Meat-to-Apple" }); + + /* add an unknown node implicitly by adding an edge */ + g.addEdge("strawberry", "apple"); + + g.removeNode("1"); + + /* layout the graph using the Spring layout implementation */ + var layouter = new Graph.Layout.Spring(g); + + /* draw the graph using the RaphaelJS draw implementation */ + renderer = new Graph.Renderer.Raphael('canvas', g, width, height); + + redraw = function() { + layouter.layout(); + renderer.draw(); + }; + hide = function(id) { + g.nodes[id].hide(); + }; + show = function(id) { + g.nodes[id].show(); + }; + // console.log(g.nodes["kiwi"]); +}; + diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000..93e0cbb --- /dev/null +++ b/web/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + +
+ + diff --git a/web/js/Curry-1.0.1.js b/web/js/Curry-1.0.1.js new file mode 100644 index 0000000..320b447 --- /dev/null +++ b/web/js/Curry-1.0.1.js @@ -0,0 +1,29 @@ +/** + * Curry - Function currying + * Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com + * Licensed under BSD (http://www.opensource.org/licenses/bsd-license.php) + * Date: 10/4/2008 + * + * @author Ariel Flesler + * @version 1.0.1 + */ + +function curry( fn ){ + return function(){ + var args = curry.args(arguments), + master = arguments.callee, + self = this; + + return args.length >= fn.length ? fn.apply(self,args) : function(){ + return master.apply( self, args.concat(curry.args(arguments)) ); + }; + }; +}; + +curry.args = function( args ){ + return Array.prototype.slice.call(args); +}; + +Function.prototype.curry = function(){ + return curry(this); +}; \ No newline at end of file diff --git a/web/js/dracula_algorithms.js b/web/js/dracula_algorithms.js new file mode 100644 index 0000000..0fbb085 --- /dev/null +++ b/web/js/dracula_algorithms.js @@ -0,0 +1,599 @@ +/* + * Various algorithms and data structures, licensed under the MIT-license. + * (c) 2010 by Johann Philipp Strathausen + * http://strathausen.eu + * + */ + + + +/* + Bellman-Ford + + Path-finding algorithm, finds the shortest paths from one node to all nodes. + + + Complexity + + O( |E| · |V| ), where E = edges and V = vertices (nodes) + + + Constraints + + Can run on graphs with negative edge weights as long as they do not have + any negative weight cycles. + + */ +function bellman_ford(g, source) { + + /* STEP 1: initialisation */ + for(var n in g.nodes) + g.nodes[n].distance = Infinity; + /* predecessors are implicitly null */ + source.distance = 0; + + step("Initially, all distances are infinite and all predecessors are null."); + + /* STEP 2: relax each edge (this is at the heart of Bellman-Ford) */ + /* repeat this for the number of nodes minus one */ + for(var i = 1; i < g.nodes.length; i++) + /* for each edge */ + for(var e in g.edges) { + var edge = g.edges[e]; + if(edge.source.distance + edge.weight < edge.target.distance) { + step("Relax edge between " + edge.source.id + " and " + edge.target.id + "."); + edge.target.distance = edge.source.distance + edge.weight; + edge.target.predecessor = edge.source; + } + //Added by Jake Stothard (Needs to be tested) + if(!edge.style.directed) { + if(edge.target.distance + edge.weight < edge.source.distance) { + g.snapShot("Relax edge between "+edge.target.id+" and "+edge.source.id+"."); + edge.source.distance = edge.target.distance + edge.weight; + edge.source.predecessor = edge.target; + } + } + } + step("Ready."); + + /* STEP 3: TODO Check for negative cycles */ + /* For now we assume here that the graph does not contain any negative + weights cycles. (this is left as an excercise to the reader[tm]) */ +} + + + +/* + Path-finding algorithm Dijkstra + + - worst-case running time is O((|E| + |V|) · log |V| ) thus better than + Bellman-Ford for sparse graphs (with less edges), but cannot handle + negative edge weights + */ +function dijkstra(g, source) { + + /* initially, all distances are infinite and all predecessors are null */ + for(var n in g.nodes) + g.nodes[n].distance = Infinity; + /* predecessors are implicitly null */ + + g.snapShot("Initially, all distances are infinite and all predecessors are null."); + + source.distance = 0; + /* set of unoptimized nodes, sorted by their distance (but a Fibonacci heap + would be better) */ + var q = new BinaryMinHeap(g.nodes, "distance"); + + /* pointer to the node in focus */ + var node; + + /* get the node with the smallest distance + as long as we have unoptimized nodes. q.min() can have O(log n). */ + while(q.min() != undefined) { + /* remove the latest */ + node = q.extractMin(); + node.optimized = true; + + /* no nodes accessible from this one, should not happen */ + if(node.distance == Infinity) + throw "Orphaned node!"; + + /* for each neighbour of node */ + for(e in node.edges) { + var other = (node == node.edges[e].target) ? node.edges[e].source : node.edges[e].target; + + if(other.optimized) + continue; + + /* look for an alternative route */ + var alt = node.distance + node.edges[e].weight; + + /* update distance and route if a better one has been found */ + if (alt < other.distance) { + + /* update distance of neighbour */ + other.distance = alt; + + /* update priority queue */ + q.heapify(); + + /* update path */ + other.predecessor = node; + g.snapShot("Enhancing node.") + } + } + } +} + + +/* All-Pairs-Shortest-Paths */ +/* Runs at worst in O(|V|³) and at best in Omega(|V|³) :-) + complexity Sigma(|V|²) */ +/* This implementation is not yet ready for general use, but works with the + Dracula graph library. */ +function floyd_warshall(g, source) { + + /* Step 1: initialising empty path matrix (second dimension is implicit) */ + var path = []; + var next = []; + var n = g.nodes.length; + + /* construct path matrix, initialize with Infinity */ + for(j in g.nodes) { + path[j] = []; + next[j] = []; + for(i in g.nodes) + path[j][i] = j == i ? 0 : Infinity; + } + + /* initialize path with edge weights */ + for(e in g.edges) + path[g.edges[e].source.id][g.edges[e].target.id] = g.edges[e].weight; + + /* Note: Usually, the initialisation is done by getting the edge weights + from a node matrix representation of the graph, not by iterating through + a list of edges as done here. */ + + /* Step 2: find best distances (the heart of Floyd-Warshall) */ + for(k in g.nodes){ + for(i in g.nodes) { + for(j in g.nodes) + if(path[i][j] > path[i][k] + path[k][j]) { + path[i][j] = path[i][k] + path[k][j]; + /* Step 2.b: remember the path */ + next[i][j] = k; + } + } + } + + /* Step 3: Path reconstruction, get shortest path */ + function getPath(i, j) { + if(path[i][j] == Infinity) + throw "There is no path."; + var intermediate = next[i][j]; + if(intermediate == undefined) + return null; + else + return getPath(i, intermediate) + .concat([intermediate]) + .concat(getPath(intermediate, j)); + } + + /* TODO use the knowledge, e.g. mark path in graph */ +} + +/* + Ford-Fulkerson + + Max-Flow-Min-Cut Algorithm finding the maximum flow through a directed + graph from source to sink. + + + Complexity + + O(E * max(f)), max(f) being the maximum flow + + + Description + + As long as there is an open path through the residual graph, send the + minimum of the residual capacities on the path. + + + Constraints + + The algorithm works only if all weights are integers. Otherwise it is + possible that the Ford–Fulkerson algorithm will not converge to the maximum + value. + + + Input + + g - Graph object + s - Source ID + t - Target (sink) ID + + + Output + + Maximum flow from Source s to Target t + + */ +/* + Edmonds-Karp + + Max-Flow-Min-Cut Algorithm finding the maximum flow through a directed + graph from source to sink. An implementation of the Ford-Fulkerson + algorithm. + + + Complexity + + O(|V|*|E|²) + + + Input + + g - Graph object (with node and edge lists, capacity is a property of edge) + s - source ID + t - sink ID + + */ +function edmonds_karp(g, s, t) { + +} + +/* + A simple binary min-heap serving as a priority queue + - takes an array as the input, with elements having a key property + - elements will look like this: + { + key: "... key property ...", + value: "... element content ..." + } + - provides insert(), min(), extractMin() and heapify() + - example usage (e.g. via the Firebug or Chromium console): + var x = {foo: 20, hui: "bla"}; + var a = new BinaryMinHeap([x,{foo:3},{foo:10},{foo:20},{foo:30},{foo:6},{foo:1},{foo:3}],"foo"); + console.log(a.extractMin()); + console.log(a.extractMin()); + x.foo = 0; // update key + a.heapify(); // call this always after having a key updated + console.log(a.extractMin()); + console.log(a.extractMin()); + - can also be used on a simple array, like [9,7,8,5] + */ +function BinaryMinHeap(array, key) { + + /* Binary tree stored in an array, no need for a complicated data structure */ + var tree = []; + + var key = key || 'key'; + + /* Calculate the index of the parent or a child */ + var parent = function(index) { return Math.floor((index - 1)/2); }; + var right = function(index) { return 2 * index + 2; }; + var left = function(index) { return 2 * index + 1; }; + + /* Helper function to swap elements with their parent + as long as the parent is bigger */ + function bubble_up(i) { + var p = parent(i); + while((p >= 0) && (tree[i][key] < tree[p][key])) { + /* swap with parent */ + tree[i] = tree.splice(p, 1, tree[i])[0]; + /* go up one level */ + i = p; + p = parent(i); + } + } + + /* Helper function to swap elements with the smaller of their children + as long as there is one */ + function bubble_down(i) { + var l = left(i); + var r = right(i); + + /* as long as there are smaller children */ + while(tree[l] && (tree[i][key] > tree[l][key]) || tree[r] && (tree[i][key] > tree[r][key])) { + + /* find smaller child */ + var child = tree[l] ? tree[r] ? tree[l][key] > tree[r][key] ? r : l : l : l; + + /* swap with smaller child with current element */ + tree[i] = tree.splice(child, 1, tree[i])[0]; + + /* go up one level */ + i = child; + l = left(i); + r = right(i); + } + } + + /* Insert a new element with respect to the heap property + 1. Insert the element at the end + 2. Bubble it up until it is smaller than its parent */ + this.insert = function(element) { + + /* make sure there's a key property */ + (element[key] == undefined) && (element = {key:element}); + + /* insert element at the end */ + tree.push(element); + + /* bubble up the element */ + bubble_up(tree.length - 1); + } + + /* Only show us the minimum */ + this.min = function() { + return tree.length == 1 ? undefined : tree[0]; + } + + /* Return and remove the minimum + 1. Take the root as the minimum that we are looking for + 2. Move the last element to the root (thereby deleting the root) + 3. Compare the new root with both of its children, swap it with the + smaller child and then check again from there (bubble down) + */ + this.extractMin = function() { + var result = this.min(); + + /* move the last element to the root or empty the tree completely */ + /* bubble down the new root if necessary */ + (tree.length == 1) && (tree = []) || (tree[0] = tree.pop()) && bubble_down(0); + + return result; + } + + /* currently unused, TODO implement */ + this.changeKey = function(index, key) { + throw "function not implemented"; + } + + this.heapify = function() { + for(var start = Math.floor((tree.length - 2) / 2); start >= 0; start--) { + bubble_down(start); + } + } + + /* insert the input elements one by one only when we don't have a key property (TODO can be done more elegant) */ + for(i in (array || [])) + this.insert(array[i]); +} + + + +/* + Quick Sort: + 1. Select some random value from the array, the median. + 2. Divide the array in three smaller arrays according to the elements + being less, equal or greater than the median. + 3. Recursively sort the array containg the elements less than the + median and the one containing elements greater than the median. + 4. Concatenate the three arrays (less, equal and greater). + 5. One or no element is always sorted. + TODO: This could be implemented more efficiently by using only one array object and several pointers. +*/ +function quickSort(arr) { + /* recursion anchor: one element is always sorted */ + if(arr.length <= 1) return arr; + /* randomly selecting some value */ + var median = arr[Math.floor(Math.random() * arr.length)]; + var arr1 = [], arr2 = [], arr3 = []; + for(var i in arr) { + arr[i] < median && arr1.push(arr[i]); + arr[i] == median && arr2.push(arr[i]); + arr[i] > median && arr3.push(arr[i]); + } + /* recursive sorting and assembling final result */ + return quickSort(arr1).concat(arr2).concat(quickSort(arr3)); +} + +/* + Selection Sort: + 1. Select the minimum and remove it from the array + 2. Sort the rest recursively + 3. Return the minimum plus the sorted rest + 4. An array with only one element is already sorted +*/ +function selectionSort(arr) { + /* recursion anchor: one element is always sorted */ + if(arr.length == 1) return arr; + var minimum = Infinity; + var index; + for(var i in arr) { + if(arr[i] < minimum) { + minimum = arr[i]; + index = i; /* remember the minimum index for later removal */ + } + } + /* remove the minimum */ + arr.splice(index, 1); + /* assemble result and sort recursively (could be easily done iteratively as well)*/ + return [minimum].concat(selectionSort(arr)); +} + +/* + Merge Sort: + 1. Cut the array in half + 2. Sort each of them recursively + 3. Merge the two sorted arrays + 4. An array with only one element is considered sorted + +*/ +function mergeSort(arr) { + /* merges two sorted arrays into one sorted array */ + function merge(a, b) { + /* result set */ + var c = []; + /* as long as there are elements in the arrays to be merged */ + while(a.length > 0 || b.length > 0){ + /* are there elements to be merged, if yes, compare them and merge */ + var n = a.length > 0 && b.length > 0 ? a[0] < b[0] ? a.shift() : b.shift() : b.length > 0 ? b.shift() : a.length > 0 ? a.shift() : null; + /* always push the smaller one onto the result set */ + n != null && c.push(n); + } + return c; + } + /* this mergeSort implementation cuts the array in half, wich should be fine with randomized arrays, but introduces the risk of a worst-case scenario */ + median = Math.floor(arr.length / 2); + var part1 = arr.slice(0, median); /* for some reason it doesn't work if inserted directly in the return statement (tried so with firefox) */ + var part2 = arr.slice(median - arr.length); + return arr.length <= 1 ? arr : merge( + mergeSort(part1), /* first half */ + mergeSort(part2) /* second half */ + ); +} + +/* Balanced Red-Black-Tree */ +function RedBlackTree(arr) { + +} + +function BTree(arr) { + +} + +function NaryTree(n, arr) { + +} + +/** + * Knuth-Morris-Pratt string matching algorithm - finds a pattern in a text. + * FIXME: Doesn't work correctly yet. + */ +function kmp(p, t) { + + /** + * PREFIX, OVERLAP or FALIURE function for KMP. Computes how many iterations + * the algorithm can skip after a mismatch. + * + * @input p - pattern (string) + * @result array of skippable iterations + */ + function prefix(p) { + /* pi contains the computed skip marks */ + var pi = [0], k = 0; + for(q = 1; q < p.length; q++) { + while(k > 0 && (p.charAt(k) != p.charAt(q))) + k = pi[k-1]; + + (p.charAt(k) == p.charAt(q)) && k++; + + pi[q] = k; + } + return pi; + } + + /* The actual KMP algorithm starts here. */ + + var pi = prefix(p), q = 0, result = []; + + for(var i = 0; i < t.length; i++) { + /* jump forward as long as the character doesn't match */ + while((q > 0) && (p.charAt(q) != t.charAt(i))) + q = pi[q]; + + (p.charAt(q) == t.charAt(i)) && q++; + + (q == p.length) && result.push(i - p.length) && (q = pi[q]); + } + + return result; +} + +/* step for algorithm visualisation */ +function step(comment, funct) { + //wait for input + //display comment (before or after waiting) +// next.wait(); + /* execute callback function */ + funct(); +} + +/** + * Curry - Function currying + * Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com + * Licensed under BSD (http://www.opensource.org/licenses/bsd-license.php) + * Date: 10/4/2008 + * + * @author Ariel Flesler + * @version 1.0.1 + */ +function curry( fn ){ + return function(){ + var args = curry.args(arguments), + master = arguments.callee, + self = this; + + return args.length >= fn.length ? fn.apply(self,args) : function(){ + return master.apply( self, args.concat(curry.args(arguments)) ); + }; + }; +}; + +curry.args = function( args ){ + return Array.prototype.slice.call(args); +}; + +Function.prototype.curry = function(){ + return curry(this); +}; + +/** + * Topological Sort + * + * Sort a directed graph based on incoming edges + * + * Coded by Jake Stothard + */ +function topological_sort(g) { + //Mark nodes as "deleted" instead of actually deleting them + //That way we don't have to copy g + + for(i in g.nodes) + g.nodes[i].deleted = false; + + var ret = topological_sort_helper(g); + + //Cleanup: Remove the deleted property + for(i in g.nodes) + delete g.nodes[i].deleted + + return ret; +} +function topological_sort_helper(g) { + //Find node with no incoming edges + var node; + for(i in g.nodes) { + if(g.nodes[i].deleted) + continue; //Bad style, meh + + var incoming = false; + for(j in g.nodes[i].edges) { + if(g.nodes[i].edges[j].target == g.nodes[i] + && g.nodes[i].edges[j].source.deleted == false) { + incoming = true; + break; + } + } + if(!incoming) { + node = g.nodes[i]; + break; + } + } + + // Either unsortable or done. Either way, GTFO + if(node == undefined) + return []; + + //"Delete" node from g + node.deleted = true; + + var tail = topological_sort_helper(g); + + tail.unshift(node); + + return tail; +} diff --git a/web/js/dracula_graffle.js b/web/js/dracula_graffle.js new file mode 100644 index 0000000..ddf171d --- /dev/null +++ b/web/js/dracula_graffle.js @@ -0,0 +1,106 @@ +/** + * Originally grabbed from the official RaphaelJS Documentation + * http://raphaeljs.com/graffle.html + * Adopted (arrows) and commented by Philipp Strathausen http://blog.ameisenbar.de + * Licenced under the MIT licence. + */ + +/** + * Usage: + * connect two shapes + * parameters: + * source shape [or connection for redrawing], + * target shape, + * style with { fg : linecolor, bg : background color, directed: boolean } + * returns: + * connection { draw = function() } + */ +Raphael.fn.connection = function (obj1, obj2, style) { + var selfRef = this; + /* create and return new connection */ + var edge = {/* + from : obj1, + to : obj2, + style : style,*/ + draw : function() { + /* get bounding boxes of target and source */ + var bb1 = obj1.getBBox(); + var bb2 = obj2.getBBox(); + var off1 = 0; + var off2 = 0; + /* coordinates for potential connection coordinates from/to the objects */ + var p = [ + {x: bb1.x + bb1.width / 2, y: bb1.y - off1}, /* NORTH 1 */ + {x: bb1.x + bb1.width / 2, y: bb1.y + bb1.height + off1}, /* SOUTH 1 */ + {x: bb1.x - off1, y: bb1.y + bb1.height / 2}, /* WEST 1 */ + {x: bb1.x + bb1.width + off1, y: bb1.y + bb1.height / 2}, /* EAST 1 */ + {x: bb2.x + bb2.width / 2, y: bb2.y - off2}, /* NORTH 2 */ + {x: bb2.x + bb2.width / 2, y: bb2.y + bb2.height + off2}, /* SOUTH 2 */ + {x: bb2.x - off2, y: bb2.y + bb2.height / 2}, /* WEST 2 */ + {x: bb2.x + bb2.width + off2, y: bb2.y + bb2.height / 2} /* EAST 2 */ + ]; + + /* distances between objects and according coordinates connection */ + var d = {}, dis = []; + + /* + * find out the best connection coordinates by trying all possible ways + */ + /* loop the first object's connection coordinates */ + for (var i = 0; i < 4; i++) { + /* loop the seond object's connection coordinates */ + for (var j = 4; j < 8; j++) { + var dx = Math.abs(p[i].x - p[j].x), + dy = Math.abs(p[i].y - p[j].y); + if ((i == j - 4) || (((i != 3 && j != 6) || p[i].x < p[j].x) && ((i != 2 && j != 7) || p[i].x > p[j].x) && ((i != 0 && j != 5) || p[i].y > p[j].y) && ((i != 1 && j != 4) || p[i].y < p[j].y))) { + dis.push(dx + dy); + d[dis[dis.length - 1].toFixed(3)] = [i, j]; + } + } + } + var res = dis.length == 0 ? [0, 4] : d[Math.min.apply(Math, dis).toFixed(3)]; + /* bezier path */ + var x1 = p[res[0]].x, + y1 = p[res[0]].y, + x4 = p[res[1]].x, + y4 = p[res[1]].y, + dx = Math.max(Math.abs(x1 - x4) / 2, 10), + dy = Math.max(Math.abs(y1 - y4) / 2, 10), + x2 = [x1, x1, x1 - dx, x1 + dx][res[0]].toFixed(3), + y2 = [y1 - dy, y1 + dy, y1, y1][res[0]].toFixed(3), + x3 = [0, 0, 0, 0, x4, x4, x4 - dx, x4 + dx][res[1]].toFixed(3), + y3 = [0, 0, 0, 0, y1 + dy, y1 - dy, y4, y4][res[1]].toFixed(3); + /* assemble path and arrow */ + var path = ["M", x1.toFixed(3), y1.toFixed(3), "C", x2, y2, x3, y3, x4.toFixed(3), y4.toFixed(3)].join(","); + /* arrow */ + if(style && style.directed) { + /* magnitude, length of the last path vector */ + var mag = Math.sqrt((y4 - y3) * (y4 - y3) + (x4 - x3) * (x4 - x3)); + /* vector normalisation to specified length */ + var norm = function(x,l){return (-x*(l||5)/mag);}; + /* calculate array coordinates (two lines orthogonal to the path vector) */ + var arr = [ + {x:(norm(x4-x3)+norm(y4-y3)+x4).toFixed(3), y:(norm(y4-y3)+norm(x4-x3)+y4).toFixed(3)}, + {x:(norm(x4-x3)-norm(y4-y3)+x4).toFixed(3), y:(norm(y4-y3)-norm(x4-x3)+y4).toFixed(3)} + ]; + path = path + ",M"+arr[0].x+","+arr[0].y+",L"+x4+","+y4+",L"+arr[1].x+","+arr[1].y; + } + /* function to be used for moving existent path(s), e.g. animate() or attr() */ + var move = "attr"; + /* applying path(s) */ + edge.fg && edge.fg[move]({path:path}) + || (edge.fg = selfRef.path(path).attr({stroke: style && style.stroke || "#000", fill: "none"}).toBack()); + edge.bg && edge.bg[move]({path:path}) + || style && style.fill && (edge.bg = style.fill.split && selfRef.path(path).attr({stroke: style.fill.split("|")[0], fill: "none", "stroke-width": style.fill.split("|")[1] || 3}).toBack()); + /* setting label */ + style && style.label + && (edge.label && edge.label.attr({x:(x1+x4)/2, y:(y1+y4)/2}) + || (edge.label = selfRef.text((x1+x4)/2, (y1+y4)/2, style.label).attr({fill: "#000", "font-size": style["font-size"] || "12px"}))); + style && style.label && style["label-style"] && edge.label && edge.label.attr(style["label-style"]); + style && style.callback && style.callback(edge); + } + } + edge.draw(); + return edge; +}; +//Raphael.prototype.set.prototype.dodo=function(){console.log("works");}; diff --git a/web/js/dracula_graph.js b/web/js/dracula_graph.js new file mode 100644 index 0000000..f3e43e1 --- /dev/null +++ b/web/js/dracula_graph.js @@ -0,0 +1,527 @@ +/* + * Dracula Graph Layout and Drawing Framework 0.0.3alpha + * (c) 2010 Philipp Strathausen , http://strathausen.eu + * Contributions by Jake Stothard . + * + * based on the Graph JavaScript framework, version 0.0.1 + * (c) 2006 Aslak Hellesoy + * (c) 2006 Dave Hoover + * + * Ported from Graph::Layouter::Spring in + * http://search.cpan.org/~pasky/Graph-Layderer-0.02/ + * The algorithm is based on a spring-style layouter of a Java-based social + * network tracker PieSpy written by Paul Mutton . + * + * This code is freely distributable under the MIT license. Commercial use is + * hereby granted without any cost or restriction. + * + * Links: + * + * Graph Dracula JavaScript Framework: + * http://graphdracula.net + * + /*--------------------------------------------------------------------------*/ + +/* + * Edge Factory + */ +var AbstractEdge = function() { +} +AbstractEdge.prototype = { + hide: function() { + this.connection.fg.hide(); + this.connection.bg && this.bg.connection.hide(); + } +}; +var EdgeFactory = function() { + this.template = new AbstractEdge(); + this.template.style = new Object(); + this.template.style.directed = false; + this.template.weight = 1; +}; +EdgeFactory.prototype = { + build: function(source, target) { + var e = jQuery.extend(true, {}, this.template); + e.source = source; + e.target = target; + return e; + } +}; + +/* + * Graph + */ +var Graph = function() { + this.nodes = {}; + this.edges = []; + this.snapshots = []; // previous graph states TODO to be implemented + this.edgeFactory = new EdgeFactory(); +}; +Graph.prototype = { + /* + * add a node + * @id the node's ID (string or number) + * @content (optional, dictionary) can contain any information that is + * being interpreted by the layout algorithm or the graph + * representation + */ + addNode: function(id, content) { + /* testing if node is already existing in the graph */ + if(this.nodes[id] == undefined) { + this.nodes[id] = new Graph.Node(id, content); + } + return this.nodes[id]; + }, + + addEdge: function(source, target, style) { + var s = this.addNode(source); + var t = this.addNode(target); + var edge = this.edgeFactory.build(s, t); + jQuery.extend(edge.style,style); + s.edges.push(edge); + this.edges.push(edge); + // NOTE: Even directed edges are added to both nodes. + t.edges.push(edge); + }, + + /* TODO to be implemented + * Preserve a copy of the graph state (nodes, positions, ...) + * @comment a comment describing the state + */ + snapShot: function(comment) { + /* FIXME + var graph = new Graph(); + graph.nodes = jQuery.extend(true, {}, this.nodes); + graph.edges = jQuery.extend(true, {}, this.edges); + this.snapshots.push({comment: comment, graph: graph}); + */ + }, + removeNode: function(id) { + delete this.nodes[id]; + for(var i = 0; i < this.edges.length; i++) { + if (this.edges[i].source.id == id || this.edges[i].target.id == id) { + this.edges.splice(i, 1); + i--; + } + } + } +}; + +/* + * Node + */ +Graph.Node = function(id, node){ + node = node || {}; + node.id = id; + node.edges = []; + node.hide = function() { + this.hidden = true; + this.shape && this.shape.hide(); /* FIXME this is representation specific code and should be elsewhere */ + for(i in this.edges) + (this.edges[i].source.id == id || this.edges[i].target == id) && this.edges[i].hide && this.edges[i].hide(); + }; + node.show = function() { + this.hidden = false; + this.shape && this.shape.show(); + for(i in this.edges) + (this.edges[i].source.id == id || this.edges[i].target == id) && this.edges[i].show && this.edges[i].show(); + }; + return node; +}; +Graph.Node.prototype = { +}; + +/* + * Renderer base class + */ +Graph.Renderer = {}; + +/* + * Renderer implementation using RaphaelJS + */ +Graph.Renderer.Raphael = function(element, graph, width, height) { + this.width = width || 400; + this.height = height || 400; + var selfRef = this; + this.r = Raphael(element, this.width, this.height); + this.radius = 40; /* max dimension of a node */ + this.graph = graph; + this.mouse_in = false; + + /* TODO default node rendering function */ + if(!this.graph.render) { + this.graph.render = function() { + return; + } + } + + /* + * Dragging + */ + this.isDrag = false; + this.dragger = function (e) { + this.dx = e.clientX; + this.dy = e.clientY; + selfRef.isDrag = this; + this.set && this.set.animate({"fill-opacity": .1}, 200) && this.set.toFront(); + e.preventDefault && e.preventDefault(); + }; + + var d = document.getElementById(element); + d.onmousemove = function (e) { + e = e || window.event; + if (selfRef.isDrag) { + var bBox = selfRef.isDrag.set.getBBox(); + // TODO round the coordinates here (eg. for proper image representation) + var newX = e.clientX - selfRef.isDrag.dx + (bBox.x + bBox.width / 2); + var newY = e.clientY - selfRef.isDrag.dy + (bBox.y + bBox.height / 2); + /* prevent shapes from being dragged out of the canvas */ + var clientX = e.clientX - (newX < 20 ? newX - 20 : newX > selfRef.width - 20 ? newX - selfRef.width + 20 : 0); + var clientY = e.clientY - (newY < 20 ? newY - 20 : newY > selfRef.height - 20 ? newY - selfRef.height + 20 : 0); + selfRef.isDrag.set.translate(clientX - Math.round(selfRef.isDrag.dx), clientY - Math.round(selfRef.isDrag.dy)); + // console.log(clientX - Math.round(selfRef.isDrag.dx), clientY - Math.round(selfRef.isDrag.dy)); + for (var i in selfRef.graph.edges) { + selfRef.graph.edges[i].connection && selfRef.graph.edges[i].connection.draw(); + } + //selfRef.r.safari(); + selfRef.isDrag.dx = clientX; + selfRef.isDrag.dy = clientY; + } + }; + d.onmouseup = function () { + selfRef.isDrag && selfRef.isDrag.set.animate({"fill-opacity": .6}, 500); + selfRef.isDrag = false; + }; + this.draw(); +}; +Graph.Renderer.Raphael.prototype = { + translate: function(point) { + return [ + (point[0] - this.graph.layoutMinX) * this.factorX + this.radius, + (point[1] - this.graph.layoutMinY) * this.factorY + this.radius + ]; + }, + + rotate: function(point, length, angle) { + var dx = length * Math.cos(angle); + var dy = length * Math.sin(angle); + return [point[0]+dx, point[1]+dy]; + }, + + draw: function() { + this.factorX = (this.width - 2 * this.radius) / (this.graph.layoutMaxX - this.graph.layoutMinX); + this.factorY = (this.height - 2 * this.radius) / (this.graph.layoutMaxY - this.graph.layoutMinY); + for (i in this.graph.nodes) { + this.drawNode(this.graph.nodes[i]); + } + for (var i = 0; i < this.graph.edges.length; i++) { + this.drawEdge(this.graph.edges[i]); + } + }, + + drawNode: function(node) { + var point = this.translate([node.layoutPosX, node.layoutPosY]); + node.point = point; + + /* if node has already been drawn, move the nodes */ + if(node.shape) { + var oBBox = node.shape.getBBox(); + var opoint = { x: oBBox.x + oBBox.width / 2, y: oBBox.y + oBBox.height / 2}; + node.shape.translate(Math.round(point[0] - opoint.x), Math.round(point[1] - opoint.y)); + this.r.safari(); + return node; + }/* else, draw new nodes */ + + var shape; + + /* if a node renderer function is provided by the user, then use it + or the default render function instead */ + if(!node.render) { + node.render = function(r, node) { + /* the default node drawing */ + var color = Raphael.getColor(); + var ellipse = r.ellipse(0, 0, 30, 20).attr({fill: color, stroke: color, "stroke-width": 2}); + /* set DOM node ID */ + ellipse.node.id = node.label || node.id; + shape = r.set(). + push(ellipse). + push(r.text(0, 30, node.label || node.id)); + return shape; + } + } + /* or check for an ajax representation of the nodes */ + if(node.shapes) { + // TODO ajax representation evaluation + } + + shape = node.render(this.r, node).hide(); + + shape.attr({"fill-opacity": .6}); + /* re-reference to the node an element belongs to, needed for dragging all elements of a node */ + shape.items.forEach(function(item){ item.set = shape; item.node.style.cursor = "move"; }); + shape.mousedown(this.dragger); + + var box = shape.getBBox(); + shape.translate(Math.round(point[0]-(box.x+box.width/2)),Math.round(point[1]-(box.y+box.height/2))) + //console.log(box,point); + node.hidden || shape.show(); + node.shape = shape; + }, + drawEdge: function(edge) { + /* if this edge already exists the other way around and is undirected */ + if(edge.backedge) + return; + if(edge.source.hidden || edge.target.hidden) { + edge.connection && edge.connection.fg.hide() | edge.connection.bg && edge.connection.bg.hide(); + return; + } + /* if edge already has been drawn, only refresh the edge */ + if(!edge.connection) { + edge.style && edge.style.callback && edge.style.callback(edge); // TODO move this somewhere else + edge.connection = this.r.connection(edge.source.shape, edge.target.shape, edge.style); + return; + } + //FIXME showing doesn't work well + edge.connection.fg.show(); + edge.connection.bg && edge.connection.bg.show(); + edge.connection.draw(); + } +}; +Graph.Layout = {}; +Graph.Layout.Spring = function(graph) { + this.graph = graph; + this.iterations = 500; + this.maxRepulsiveForceDistance = 6; + this.k = 2; + this.c = 0.01; + this.maxVertexMovement = 0.5; + this.layout(); +}; +Graph.Layout.Spring.prototype = { + layout: function() { + this.layoutPrepare(); + for (var i = 0; i < this.iterations; i++) { + this.layoutIteration(); + } + this.layoutCalcBounds(); + }, + + layoutPrepare: function() { + for (i in this.graph.nodes) { + var node = this.graph.nodes[i]; + node.layoutPosX = 0; + node.layoutPosY = 0; + node.layoutForceX = 0; + node.layoutForceY = 0; + } + + }, + + layoutCalcBounds: function() { + var minx = Infinity, maxx = -Infinity, miny = Infinity, maxy = -Infinity; + + for (i in this.graph.nodes) { + var x = this.graph.nodes[i].layoutPosX; + var y = this.graph.nodes[i].layoutPosY; + + if(x > maxx) maxx = x; + if(x < minx) minx = x; + if(y > maxy) maxy = y; + if(y < miny) miny = y; + } + + this.graph.layoutMinX = minx; + this.graph.layoutMaxX = maxx; + this.graph.layoutMinY = miny; + this.graph.layoutMaxY = maxy; + }, + + layoutIteration: function() { + // Forces on nodes due to node-node repulsions + + var prev = new Array(); + for(var c in this.graph.nodes) { + var node1 = this.graph.nodes[c]; + for (var d in prev) { + var node2 = this.graph.nodes[prev[d]]; + this.layoutRepulsive(node1, node2); + + } + prev.push(c); + } + + // Forces on nodes due to edge attractions + for (var i = 0; i < this.graph.edges.length; i++) { + var edge = this.graph.edges[i]; + this.layoutAttractive(edge); + } + + // Move by the given force + for (i in this.graph.nodes) { + var node = this.graph.nodes[i]; + var xmove = this.c * node.layoutForceX; + var ymove = this.c * node.layoutForceY; + + var max = this.maxVertexMovement; + if(xmove > max) xmove = max; + if(xmove < -max) xmove = -max; + if(ymove > max) ymove = max; + if(ymove < -max) ymove = -max; + + node.layoutPosX += xmove; + node.layoutPosY += ymove; + node.layoutForceX = 0; + node.layoutForceY = 0; + } + }, + + layoutRepulsive: function(node1, node2) { + if (typeof node1 == 'undefined' || typeof node2 == 'undefined') + return; + var dx = node2.layoutPosX - node1.layoutPosX; + var dy = node2.layoutPosY - node1.layoutPosY; + var d2 = dx * dx + dy * dy; + if(d2 < 0.01) { + dx = 0.1 * Math.random() + 0.1; + dy = 0.1 * Math.random() + 0.1; + var d2 = dx * dx + dy * dy; + } + var d = Math.sqrt(d2); + if(d < this.maxRepulsiveForceDistance) { + var repulsiveForce = this.k * this.k / d; + node2.layoutForceX += repulsiveForce * dx / d; + node2.layoutForceY += repulsiveForce * dy / d; + node1.layoutForceX -= repulsiveForce * dx / d; + node1.layoutForceY -= repulsiveForce * dy / d; + } + }, + + layoutAttractive: function(edge) { + var node1 = edge.source; + var node2 = edge.target; + + var dx = node2.layoutPosX - node1.layoutPosX; + var dy = node2.layoutPosY - node1.layoutPosY; + var d2 = dx * dx + dy * dy; + if(d2 < 0.01) { + dx = 0.1 * Math.random() + 0.1; + dy = 0.1 * Math.random() + 0.1; + var d2 = dx * dx + dy * dy; + } + var d = Math.sqrt(d2); + if(d > this.maxRepulsiveForceDistance) { + d = this.maxRepulsiveForceDistance; + d2 = d * d; + } + var attractiveForce = (d2 - this.k * this.k) / this.k; + if(edge.attraction == undefined) edge.attraction = 1; + attractiveForce *= Math.log(edge.attraction) * 0.5 + 1; + + node2.layoutForceX -= attractiveForce * dx / d; + node2.layoutForceY -= attractiveForce * dy / d; + node1.layoutForceX += attractiveForce * dx / d; + node1.layoutForceY += attractiveForce * dy / d; + } +}; + +Graph.Layout.Ordered = function(graph, order) { + this.graph = graph; + this.order = order; + this.layout(); +}; +Graph.Layout.Ordered.prototype = { + layout: function() { + this.layoutPrepare(); + this.layoutCalcBounds(); + }, + + layoutPrepare: function(order) { + for (i in this.graph.nodes) { + var node = this.graph.nodes[i]; + node.layoutPosX = 0; + node.layoutPosY = 0; + } + var counter = 0; + for (i in this.order) { + var node = this.order[i]; + node.layoutPosX = counter; + node.layoutPosY = Math.random(); + counter++; + } + }, + + layoutCalcBounds: function() { + var minx = Infinity, maxx = -Infinity, miny = Infinity, maxy = -Infinity; + + for (i in this.graph.nodes) { + var x = this.graph.nodes[i].layoutPosX; + var y = this.graph.nodes[i].layoutPosY; + + if(x > maxx) maxx = x; + if(x < minx) minx = x; + if(y > maxy) maxy = y; + if(y < miny) miny = y; + } + + this.graph.layoutMinX = minx; + this.graph.layoutMaxX = maxx; + + this.graph.layoutMinY = miny; + this.graph.layoutMaxY = maxy; + } +}; + +/* + * usefull JavaScript extensions, + */ + +function log(a) {console.log&&console.log(a);} + +/* + * Raphael Tooltip Plugin + * - attaches an element as a tooltip to another element + * + * Usage example, adding a rectangle as a tooltip to a circle: + * + * paper.circle(100,100,10).tooltip(paper.rect(0,0,20,30)); + * + * If you want to use more shapes, you'll have to put them into a set. + * + */ +Raphael.el.tooltip = function (tp) { + this.tp = tp; + this.tp.o = {x: 0, y: 0}; + this.tp.hide(); + this.hover( + function(event){ + this.mousemove(function(event){ + this.tp.translate(event.clientX - + this.tp.o.x,event.clientY - this.tp.o.y); + this.tp.o = {x: event.clientX, y: event.clientY}; + }); + this.tp.show().toFront(); + }, + function(event){ + this.tp.hide(); + this.unmousemove(); + }); + return this; +}; + +/* For IE */ +if (!Array.prototype.forEach) +{ + Array.prototype.forEach = function(fun /*, thisp*/) + { + var len = this.length; + if (typeof fun != "function") + throw new TypeError(); + + var thisp = arguments[1]; + for (var i = 0; i < len; i++) + { + if (i in this) + fun.call(thisp, this[i], i, this); + } + }; +} diff --git a/web/js/jquery-1.4.2.min.js b/web/js/jquery-1.4.2.min.js new file mode 100644 index 0000000..7c24308 --- /dev/null +++ b/web/js/jquery-1.4.2.min.js @@ -0,0 +1,154 @@ +/*! + * jQuery JavaScript Library v1.4.2 + * http://jquery.com/ + * + * Copyright 2010, John Resig + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * Copyright 2010, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * + * Date: Sat Feb 13 22:33:48 2010 -0500 + */ +(function(A,w){function ma(){if(!c.isReady){try{s.documentElement.doScroll("left")}catch(a){setTimeout(ma,1);return}c.ready()}}function Qa(a,b){b.src?c.ajax({url:b.src,async:false,dataType:"script"}):c.globalEval(b.text||b.textContent||b.innerHTML||"");b.parentNode&&b.parentNode.removeChild(b)}function X(a,b,d,f,e,j){var i=a.length;if(typeof b==="object"){for(var o in b)X(a,o,b[o],f,e,d);return a}if(d!==w){f=!j&&f&&c.isFunction(d);for(o=0;o)[^>]*$|^#([\w-]+)$/,Ua=/^.[^:#\[\.,]*$/,Va=/\S/, +Wa=/^(\s|\u00A0)+|(\s|\u00A0)+$/g,Xa=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,P=navigator.userAgent,xa=false,Q=[],L,$=Object.prototype.toString,aa=Object.prototype.hasOwnProperty,ba=Array.prototype.push,R=Array.prototype.slice,ya=Array.prototype.indexOf;c.fn=c.prototype={init:function(a,b){var d,f;if(!a)return this;if(a.nodeType){this.context=this[0]=a;this.length=1;return this}if(a==="body"&&!b){this.context=s;this[0]=s.body;this.selector="body";this.length=1;return this}if(typeof a==="string")if((d=Ta.exec(a))&& +(d[1]||!b))if(d[1]){f=b?b.ownerDocument||b:s;if(a=Xa.exec(a))if(c.isPlainObject(b)){a=[s.createElement(a[1])];c.fn.attr.call(a,b,true)}else a=[f.createElement(a[1])];else{a=sa([d[1]],[f]);a=(a.cacheable?a.fragment.cloneNode(true):a.fragment).childNodes}return c.merge(this,a)}else{if(b=s.getElementById(d[2])){if(b.id!==d[2])return T.find(a);this.length=1;this[0]=b}this.context=s;this.selector=a;return this}else if(!b&&/^\w+$/.test(a)){this.selector=a;this.context=s;a=s.getElementsByTagName(a);return c.merge(this, +a)}else return!b||b.jquery?(b||T).find(a):c(b).find(a);else if(c.isFunction(a))return T.ready(a);if(a.selector!==w){this.selector=a.selector;this.context=a.context}return c.makeArray(a,this)},selector:"",jquery:"1.4.2",length:0,size:function(){return this.length},toArray:function(){return R.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this.slice(a)[0]:this[a]},pushStack:function(a,b,d){var f=c();c.isArray(a)?ba.apply(f,a):c.merge(f,a);f.prevObject=this;f.context=this.context;if(b=== +"find")f.selector=this.selector+(this.selector?" ":"")+d;else if(b)f.selector=this.selector+"."+b+"("+d+")";return f},each:function(a,b){return c.each(this,a,b)},ready:function(a){c.bindReady();if(c.isReady)a.call(s,c);else Q&&Q.push(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(R.apply(this,arguments),"slice",R.call(arguments).join(","))},map:function(a){return this.pushStack(c.map(this, +function(b,d){return a.call(b,d,b)}))},end:function(){return this.prevObject||c(null)},push:ba,sort:[].sort,splice:[].splice};c.fn.init.prototype=c.fn;c.extend=c.fn.extend=function(){var a=arguments[0]||{},b=1,d=arguments.length,f=false,e,j,i,o;if(typeof a==="boolean"){f=a;a=arguments[1]||{};b=2}if(typeof a!=="object"&&!c.isFunction(a))a={};if(d===b){a=this;--b}for(;b
a"; +var e=d.getElementsByTagName("*"),j=d.getElementsByTagName("a")[0];if(!(!e||!e.length||!j)){c.support={leadingWhitespace:d.firstChild.nodeType===3,tbody:!d.getElementsByTagName("tbody").length,htmlSerialize:!!d.getElementsByTagName("link").length,style:/red/.test(j.getAttribute("style")),hrefNormalized:j.getAttribute("href")==="/a",opacity:/^0.55$/.test(j.style.opacity),cssFloat:!!j.style.cssFloat,checkOn:d.getElementsByTagName("input")[0].value==="on",optSelected:s.createElement("select").appendChild(s.createElement("option")).selected, +parentNode:d.removeChild(d.appendChild(s.createElement("div"))).parentNode===null,deleteExpando:true,checkClone:false,scriptEval:false,noCloneEvent:true,boxModel:null};b.type="text/javascript";try{b.appendChild(s.createTextNode("window."+f+"=1;"))}catch(i){}a.insertBefore(b,a.firstChild);if(A[f]){c.support.scriptEval=true;delete A[f]}try{delete b.test}catch(o){c.support.deleteExpando=false}a.removeChild(b);if(d.attachEvent&&d.fireEvent){d.attachEvent("onclick",function k(){c.support.noCloneEvent= +false;d.detachEvent("onclick",k)});d.cloneNode(true).fireEvent("onclick")}d=s.createElement("div");d.innerHTML="";a=s.createDocumentFragment();a.appendChild(d.firstChild);c.support.checkClone=a.cloneNode(true).cloneNode(true).lastChild.checked;c(function(){var k=s.createElement("div");k.style.width=k.style.paddingLeft="1px";s.body.appendChild(k);c.boxModel=c.support.boxModel=k.offsetWidth===2;s.body.removeChild(k).style.display="none"});a=function(k){var n= +s.createElement("div");k="on"+k;var r=k in n;if(!r){n.setAttribute(k,"return;");r=typeof n[k]==="function"}return r};c.support.submitBubbles=a("submit");c.support.changeBubbles=a("change");a=b=d=e=j=null}})();c.props={"for":"htmlFor","class":"className",readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing",rowspan:"rowSpan",colspan:"colSpan",tabindex:"tabIndex",usemap:"useMap",frameborder:"frameBorder"};var G="jQuery"+J(),Ya=0,za={};c.extend({cache:{},expando:G,noData:{embed:true,object:true, +applet:true},data:function(a,b,d){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var f=a[G],e=c.cache;if(!f&&typeof b==="string"&&d===w)return null;f||(f=++Ya);if(typeof b==="object"){a[G]=f;e[f]=c.extend(true,{},b)}else if(!e[f]){a[G]=f;e[f]={}}a=e[f];if(d!==w)a[b]=d;return typeof b==="string"?a[b]:a}},removeData:function(a,b){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var d=a[G],f=c.cache,e=f[d];if(b){if(e){delete e[b];c.isEmptyObject(e)&&c.removeData(a)}}else{if(c.support.deleteExpando)delete a[c.expando]; +else a.removeAttribute&&a.removeAttribute(c.expando);delete f[d]}}}});c.fn.extend({data:function(a,b){if(typeof a==="undefined"&&this.length)return c.data(this[0]);else if(typeof a==="object")return this.each(function(){c.data(this,a)});var d=a.split(".");d[1]=d[1]?"."+d[1]:"";if(b===w){var f=this.triggerHandler("getData"+d[1]+"!",[d[0]]);if(f===w&&this.length)f=c.data(this[0],a);return f===w&&d[1]?this.data(d[0]):f}else return this.trigger("setData"+d[1]+"!",[d[0],b]).each(function(){c.data(this, +a,b)})},removeData:function(a){return this.each(function(){c.removeData(this,a)})}});c.extend({queue:function(a,b,d){if(a){b=(b||"fx")+"queue";var f=c.data(a,b);if(!d)return f||[];if(!f||c.isArray(d))f=c.data(a,b,c.makeArray(d));else f.push(d);return f}},dequeue:function(a,b){b=b||"fx";var d=c.queue(a,b),f=d.shift();if(f==="inprogress")f=d.shift();if(f){b==="fx"&&d.unshift("inprogress");f.call(a,function(){c.dequeue(a,b)})}}});c.fn.extend({queue:function(a,b){if(typeof a!=="string"){b=a;a="fx"}if(b=== +w)return c.queue(this[0],a);return this.each(function(){var d=c.queue(this,a,b);a==="fx"&&d[0]!=="inprogress"&&c.dequeue(this,a)})},dequeue:function(a){return this.each(function(){c.dequeue(this,a)})},delay:function(a,b){a=c.fx?c.fx.speeds[a]||a:a;b=b||"fx";return this.queue(b,function(){var d=this;setTimeout(function(){c.dequeue(d,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])}});var Aa=/[\n\t]/g,ca=/\s+/,Za=/\r/g,$a=/href|src|style/,ab=/(button|input)/i,bb=/(button|input|object|select|textarea)/i, +cb=/^(a|area)$/i,Ba=/radio|checkbox/;c.fn.extend({attr:function(a,b){return X(this,a,b,true,c.attr)},removeAttr:function(a){return this.each(function(){c.attr(this,a,"");this.nodeType===1&&this.removeAttribute(a)})},addClass:function(a){if(c.isFunction(a))return this.each(function(n){var r=c(this);r.addClass(a.call(this,n,r.attr("class")))});if(a&&typeof a==="string")for(var b=(a||"").split(ca),d=0,f=this.length;d-1)return true;return false},val:function(a){if(a===w){var b=this[0];if(b){if(c.nodeName(b,"option"))return(b.attributes.value||{}).specified?b.value:b.text;if(c.nodeName(b,"select")){var d=b.selectedIndex,f=[],e=b.options;b=b.type==="select-one";if(d<0)return null;var j=b?d:0;for(d=b?d+1:e.length;j=0;else if(c.nodeName(this,"select")){var u=c.makeArray(r);c("option",this).each(function(){this.selected= +c.inArray(c(this).val(),u)>=0});if(!u.length)this.selectedIndex=-1}else this.value=r}})}});c.extend({attrFn:{val:true,css:true,html:true,text:true,data:true,width:true,height:true,offset:true},attr:function(a,b,d,f){if(!a||a.nodeType===3||a.nodeType===8)return w;if(f&&b in c.attrFn)return c(a)[b](d);f=a.nodeType!==1||!c.isXMLDoc(a);var e=d!==w;b=f&&c.props[b]||b;if(a.nodeType===1){var j=$a.test(b);if(b in a&&f&&!j){if(e){b==="type"&&ab.test(a.nodeName)&&a.parentNode&&c.error("type property can't be changed"); +a[b]=d}if(c.nodeName(a,"form")&&a.getAttributeNode(b))return a.getAttributeNode(b).nodeValue;if(b==="tabIndex")return(b=a.getAttributeNode("tabIndex"))&&b.specified?b.value:bb.test(a.nodeName)||cb.test(a.nodeName)&&a.href?0:w;return a[b]}if(!c.support.style&&f&&b==="style"){if(e)a.style.cssText=""+d;return a.style.cssText}e&&a.setAttribute(b,""+d);a=!c.support.hrefNormalized&&f&&j?a.getAttribute(b,2):a.getAttribute(b);return a===null?w:a}return c.style(a,b,d)}});var O=/\.(.*)$/,db=function(a){return a.replace(/[^\w\s\.\|`]/g, +function(b){return"\\"+b})};c.event={add:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){if(a.setInterval&&a!==A&&!a.frameElement)a=A;var e,j;if(d.handler){e=d;d=e.handler}if(!d.guid)d.guid=c.guid++;if(j=c.data(a)){var i=j.events=j.events||{},o=j.handle;if(!o)j.handle=o=function(){return typeof c!=="undefined"&&!c.event.triggered?c.event.handle.apply(o.elem,arguments):w};o.elem=a;b=b.split(" ");for(var k,n=0,r;k=b[n++];){j=e?c.extend({},e):{handler:d,data:f};if(k.indexOf(".")>-1){r=k.split("."); +k=r.shift();j.namespace=r.slice(0).sort().join(".")}else{r=[];j.namespace=""}j.type=k;j.guid=d.guid;var u=i[k],z=c.event.special[k]||{};if(!u){u=i[k]=[];if(!z.setup||z.setup.call(a,f,r,o)===false)if(a.addEventListener)a.addEventListener(k,o,false);else a.attachEvent&&a.attachEvent("on"+k,o)}if(z.add){z.add.call(a,j);if(!j.handler.guid)j.handler.guid=d.guid}u.push(j);c.event.global[k]=true}a=null}}},global:{},remove:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){var e,j=0,i,o,k,n,r,u,z=c.data(a), +C=z&&z.events;if(z&&C){if(b&&b.type){d=b.handler;b=b.type}if(!b||typeof b==="string"&&b.charAt(0)==="."){b=b||"";for(e in C)c.event.remove(a,e+b)}else{for(b=b.split(" ");e=b[j++];){n=e;i=e.indexOf(".")<0;o=[];if(!i){o=e.split(".");e=o.shift();k=new RegExp("(^|\\.)"+c.map(o.slice(0).sort(),db).join("\\.(?:.*\\.)?")+"(\\.|$)")}if(r=C[e])if(d){n=c.event.special[e]||{};for(B=f||0;B=0){a.type= +e=e.slice(0,-1);a.exclusive=true}if(!d){a.stopPropagation();c.event.global[e]&&c.each(c.cache,function(){this.events&&this.events[e]&&c.event.trigger(a,b,this.handle.elem)})}if(!d||d.nodeType===3||d.nodeType===8)return w;a.result=w;a.target=d;b=c.makeArray(b);b.unshift(a)}a.currentTarget=d;(f=c.data(d,"handle"))&&f.apply(d,b);f=d.parentNode||d.ownerDocument;try{if(!(d&&d.nodeName&&c.noData[d.nodeName.toLowerCase()]))if(d["on"+e]&&d["on"+e].apply(d,b)===false)a.result=false}catch(j){}if(!a.isPropagationStopped()&& +f)c.event.trigger(a,b,f,true);else if(!a.isDefaultPrevented()){f=a.target;var i,o=c.nodeName(f,"a")&&e==="click",k=c.event.special[e]||{};if((!k._default||k._default.call(d,a)===false)&&!o&&!(f&&f.nodeName&&c.noData[f.nodeName.toLowerCase()])){try{if(f[e]){if(i=f["on"+e])f["on"+e]=null;c.event.triggered=true;f[e]()}}catch(n){}if(i)f["on"+e]=i;c.event.triggered=false}}},handle:function(a){var b,d,f,e;a=arguments[0]=c.event.fix(a||A.event);a.currentTarget=this;b=a.type.indexOf(".")<0&&!a.exclusive; +if(!b){d=a.type.split(".");a.type=d.shift();f=new RegExp("(^|\\.)"+d.slice(0).sort().join("\\.(?:.*\\.)?")+"(\\.|$)")}e=c.data(this,"events");d=e[a.type];if(e&&d){d=d.slice(0);e=0;for(var j=d.length;e-1?c.map(a.options,function(f){return f.selected}).join("-"):"";else if(a.nodeName.toLowerCase()==="select")d=a.selectedIndex;return d},fa=function(a,b){var d=a.target,f,e;if(!(!da.test(d.nodeName)||d.readOnly)){f=c.data(d,"_change_data");e=Fa(d);if(a.type!=="focusout"||d.type!=="radio")c.data(d,"_change_data", +e);if(!(f===w||e===f))if(f!=null||e){a.type="change";return c.event.trigger(a,b,d)}}};c.event.special.change={filters:{focusout:fa,click:function(a){var b=a.target,d=b.type;if(d==="radio"||d==="checkbox"||b.nodeName.toLowerCase()==="select")return fa.call(this,a)},keydown:function(a){var b=a.target,d=b.type;if(a.keyCode===13&&b.nodeName.toLowerCase()!=="textarea"||a.keyCode===32&&(d==="checkbox"||d==="radio")||d==="select-multiple")return fa.call(this,a)},beforeactivate:function(a){a=a.target;c.data(a, +"_change_data",Fa(a))}},setup:function(){if(this.type==="file")return false;for(var a in ea)c.event.add(this,a+".specialChange",ea[a]);return da.test(this.nodeName)},teardown:function(){c.event.remove(this,".specialChange");return da.test(this.nodeName)}};ea=c.event.special.change.filters}s.addEventListener&&c.each({focus:"focusin",blur:"focusout"},function(a,b){function d(f){f=c.event.fix(f);f.type=b;return c.event.handle.call(this,f)}c.event.special[b]={setup:function(){this.addEventListener(a, +d,true)},teardown:function(){this.removeEventListener(a,d,true)}}});c.each(["bind","one"],function(a,b){c.fn[b]=function(d,f,e){if(typeof d==="object"){for(var j in d)this[b](j,f,d[j],e);return this}if(c.isFunction(f)){e=f;f=w}var i=b==="one"?c.proxy(e,function(k){c(this).unbind(k,i);return e.apply(this,arguments)}):e;if(d==="unload"&&b!=="one")this.one(d,f,e);else{j=0;for(var o=this.length;j0){y=t;break}}t=t[g]}m[q]=y}}}var f=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, +e=0,j=Object.prototype.toString,i=false,o=true;[0,0].sort(function(){o=false;return 0});var k=function(g,h,l,m){l=l||[];var q=h=h||s;if(h.nodeType!==1&&h.nodeType!==9)return[];if(!g||typeof g!=="string")return l;for(var p=[],v,t,y,S,H=true,M=x(h),I=g;(f.exec(""),v=f.exec(I))!==null;){I=v[3];p.push(v[1]);if(v[2]){S=v[3];break}}if(p.length>1&&r.exec(g))if(p.length===2&&n.relative[p[0]])t=ga(p[0]+p[1],h);else for(t=n.relative[p[0]]?[h]:k(p.shift(),h);p.length;){g=p.shift();if(n.relative[g])g+=p.shift(); +t=ga(g,t)}else{if(!m&&p.length>1&&h.nodeType===9&&!M&&n.match.ID.test(p[0])&&!n.match.ID.test(p[p.length-1])){v=k.find(p.shift(),h,M);h=v.expr?k.filter(v.expr,v.set)[0]:v.set[0]}if(h){v=m?{expr:p.pop(),set:z(m)}:k.find(p.pop(),p.length===1&&(p[0]==="~"||p[0]==="+")&&h.parentNode?h.parentNode:h,M);t=v.expr?k.filter(v.expr,v.set):v.set;if(p.length>0)y=z(t);else H=false;for(;p.length;){var D=p.pop();v=D;if(n.relative[D])v=p.pop();else D="";if(v==null)v=h;n.relative[D](y,v,M)}}else y=[]}y||(y=t);y||k.error(D|| +g);if(j.call(y)==="[object Array]")if(H)if(h&&h.nodeType===1)for(g=0;y[g]!=null;g++){if(y[g]&&(y[g]===true||y[g].nodeType===1&&E(h,y[g])))l.push(t[g])}else for(g=0;y[g]!=null;g++)y[g]&&y[g].nodeType===1&&l.push(t[g]);else l.push.apply(l,y);else z(y,l);if(S){k(S,q,l,m);k.uniqueSort(l)}return l};k.uniqueSort=function(g){if(B){i=o;g.sort(B);if(i)for(var h=1;h":function(g,h){var l=typeof h==="string";if(l&&!/\W/.test(h)){h=h.toLowerCase();for(var m=0,q=g.length;m=0))l||m.push(v);else if(l)h[p]=false;return false},ID:function(g){return g[1].replace(/\\/g,"")},TAG:function(g){return g[1].toLowerCase()}, +CHILD:function(g){if(g[1]==="nth"){var h=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(g[2]==="even"&&"2n"||g[2]==="odd"&&"2n+1"||!/\D/.test(g[2])&&"0n+"+g[2]||g[2]);g[2]=h[1]+(h[2]||1)-0;g[3]=h[3]-0}g[0]=e++;return g},ATTR:function(g,h,l,m,q,p){h=g[1].replace(/\\/g,"");if(!p&&n.attrMap[h])g[1]=n.attrMap[h];if(g[2]==="~=")g[4]=" "+g[4]+" ";return g},PSEUDO:function(g,h,l,m,q){if(g[1]==="not")if((f.exec(g[3])||"").length>1||/^\w/.test(g[3]))g[3]=k(g[3],null,null,h);else{g=k.filter(g[3],h,l,true^q);l||m.push.apply(m, +g);return false}else if(n.match.POS.test(g[0])||n.match.CHILD.test(g[0]))return true;return g},POS:function(g){g.unshift(true);return g}},filters:{enabled:function(g){return g.disabled===false&&g.type!=="hidden"},disabled:function(g){return g.disabled===true},checked:function(g){return g.checked===true},selected:function(g){return g.selected===true},parent:function(g){return!!g.firstChild},empty:function(g){return!g.firstChild},has:function(g,h,l){return!!k(l[3],g).length},header:function(g){return/h\d/i.test(g.nodeName)}, +text:function(g){return"text"===g.type},radio:function(g){return"radio"===g.type},checkbox:function(g){return"checkbox"===g.type},file:function(g){return"file"===g.type},password:function(g){return"password"===g.type},submit:function(g){return"submit"===g.type},image:function(g){return"image"===g.type},reset:function(g){return"reset"===g.type},button:function(g){return"button"===g.type||g.nodeName.toLowerCase()==="button"},input:function(g){return/input|select|textarea|button/i.test(g.nodeName)}}, +setFilters:{first:function(g,h){return h===0},last:function(g,h,l,m){return h===m.length-1},even:function(g,h){return h%2===0},odd:function(g,h){return h%2===1},lt:function(g,h,l){return hl[3]-0},nth:function(g,h,l){return l[3]-0===h},eq:function(g,h,l){return l[3]-0===h}},filter:{PSEUDO:function(g,h,l,m){var q=h[1],p=n.filters[q];if(p)return p(g,l,h,m);else if(q==="contains")return(g.textContent||g.innerText||a([g])||"").indexOf(h[3])>=0;else if(q==="not"){h= +h[3];l=0;for(m=h.length;l=0}},ID:function(g,h){return g.nodeType===1&&g.getAttribute("id")===h},TAG:function(g,h){return h==="*"&&g.nodeType===1||g.nodeName.toLowerCase()===h},CLASS:function(g,h){return(" "+(g.className||g.getAttribute("class"))+" ").indexOf(h)>-1},ATTR:function(g,h){var l=h[1];g=n.attrHandle[l]?n.attrHandle[l](g):g[l]!=null?g[l]:g.getAttribute(l);l=g+"";var m=h[2];h=h[4];return g==null?m==="!=":m=== +"="?l===h:m==="*="?l.indexOf(h)>=0:m==="~="?(" "+l+" ").indexOf(h)>=0:!h?l&&g!==false:m==="!="?l!==h:m==="^="?l.indexOf(h)===0:m==="$="?l.substr(l.length-h.length)===h:m==="|="?l===h||l.substr(0,h.length+1)===h+"-":false},POS:function(g,h,l,m){var q=n.setFilters[h[2]];if(q)return q(g,l,h,m)}}},r=n.match.POS;for(var u in n.match){n.match[u]=new RegExp(n.match[u].source+/(?![^\[]*\])(?![^\(]*\))/.source);n.leftMatch[u]=new RegExp(/(^(?:.|\r|\n)*?)/.source+n.match[u].source.replace(/\\(\d+)/g,function(g, +h){return"\\"+(h-0+1)}))}var z=function(g,h){g=Array.prototype.slice.call(g,0);if(h){h.push.apply(h,g);return h}return g};try{Array.prototype.slice.call(s.documentElement.childNodes,0)}catch(C){z=function(g,h){h=h||[];if(j.call(g)==="[object Array]")Array.prototype.push.apply(h,g);else if(typeof g.length==="number")for(var l=0,m=g.length;l";var l=s.documentElement;l.insertBefore(g,l.firstChild);if(s.getElementById(h)){n.find.ID=function(m,q,p){if(typeof q.getElementById!=="undefined"&&!p)return(q=q.getElementById(m[1]))?q.id===m[1]||typeof q.getAttributeNode!=="undefined"&& +q.getAttributeNode("id").nodeValue===m[1]?[q]:w:[]};n.filter.ID=function(m,q){var p=typeof m.getAttributeNode!=="undefined"&&m.getAttributeNode("id");return m.nodeType===1&&p&&p.nodeValue===q}}l.removeChild(g);l=g=null})();(function(){var g=s.createElement("div");g.appendChild(s.createComment(""));if(g.getElementsByTagName("*").length>0)n.find.TAG=function(h,l){l=l.getElementsByTagName(h[1]);if(h[1]==="*"){h=[];for(var m=0;l[m];m++)l[m].nodeType===1&&h.push(l[m]);l=h}return l};g.innerHTML=""; +if(g.firstChild&&typeof g.firstChild.getAttribute!=="undefined"&&g.firstChild.getAttribute("href")!=="#")n.attrHandle.href=function(h){return h.getAttribute("href",2)};g=null})();s.querySelectorAll&&function(){var g=k,h=s.createElement("div");h.innerHTML="

";if(!(h.querySelectorAll&&h.querySelectorAll(".TEST").length===0)){k=function(m,q,p,v){q=q||s;if(!v&&q.nodeType===9&&!x(q))try{return z(q.querySelectorAll(m),p)}catch(t){}return g(m,q,p,v)};for(var l in g)k[l]=g[l];h=null}}(); +(function(){var g=s.createElement("div");g.innerHTML="
";if(!(!g.getElementsByClassName||g.getElementsByClassName("e").length===0)){g.lastChild.className="e";if(g.getElementsByClassName("e").length!==1){n.order.splice(1,0,"CLASS");n.find.CLASS=function(h,l,m){if(typeof l.getElementsByClassName!=="undefined"&&!m)return l.getElementsByClassName(h[1])};g=null}}})();var E=s.compareDocumentPosition?function(g,h){return!!(g.compareDocumentPosition(h)&16)}: +function(g,h){return g!==h&&(g.contains?g.contains(h):true)},x=function(g){return(g=(g?g.ownerDocument||g:0).documentElement)?g.nodeName!=="HTML":false},ga=function(g,h){var l=[],m="",q;for(h=h.nodeType?[h]:h;q=n.match.PSEUDO.exec(g);){m+=q[0];g=g.replace(n.match.PSEUDO,"")}g=n.relative[g]?g+"*":g;q=0;for(var p=h.length;q=0===d})};c.fn.extend({find:function(a){for(var b=this.pushStack("","find",a),d=0,f=0,e=this.length;f0)for(var j=d;j0},closest:function(a,b){if(c.isArray(a)){var d=[],f=this[0],e,j= +{},i;if(f&&a.length){e=0;for(var o=a.length;e-1:c(f).is(e)){d.push({selector:i,elem:f});delete j[i]}}f=f.parentNode}}return d}var k=c.expr.match.POS.test(a)?c(a,b||this.context):null;return this.map(function(n,r){for(;r&&r.ownerDocument&&r!==b;){if(k?k.index(r)>-1:c(r).is(a))return r;r=r.parentNode}return null})},index:function(a){if(!a||typeof a=== +"string")return c.inArray(this[0],a?c(a):this.parent().children());return c.inArray(a.jquery?a[0]:a,this)},add:function(a,b){a=typeof a==="string"?c(a,b||this.context):c.makeArray(a);b=c.merge(this.get(),a);return this.pushStack(qa(a[0])||qa(b[0])?b:c.unique(b))},andSelf:function(){return this.add(this.prevObject)}});c.each({parent:function(a){return(a=a.parentNode)&&a.nodeType!==11?a:null},parents:function(a){return c.dir(a,"parentNode")},parentsUntil:function(a,b,d){return c.dir(a,"parentNode", +d)},next:function(a){return c.nth(a,2,"nextSibling")},prev:function(a){return c.nth(a,2,"previousSibling")},nextAll:function(a){return c.dir(a,"nextSibling")},prevAll:function(a){return c.dir(a,"previousSibling")},nextUntil:function(a,b,d){return c.dir(a,"nextSibling",d)},prevUntil:function(a,b,d){return c.dir(a,"previousSibling",d)},siblings:function(a){return c.sibling(a.parentNode.firstChild,a)},children:function(a){return c.sibling(a.firstChild)},contents:function(a){return c.nodeName(a,"iframe")? +a.contentDocument||a.contentWindow.document:c.makeArray(a.childNodes)}},function(a,b){c.fn[a]=function(d,f){var e=c.map(this,b,d);eb.test(a)||(f=d);if(f&&typeof f==="string")e=c.filter(f,e);e=this.length>1?c.unique(e):e;if((this.length>1||gb.test(f))&&fb.test(a))e=e.reverse();return this.pushStack(e,a,R.call(arguments).join(","))}});c.extend({filter:function(a,b,d){if(d)a=":not("+a+")";return c.find.matches(a,b)},dir:function(a,b,d){var f=[];for(a=a[b];a&&a.nodeType!==9&&(d===w||a.nodeType!==1||!c(a).is(d));){a.nodeType=== +1&&f.push(a);a=a[b]}return f},nth:function(a,b,d){b=b||1;for(var f=0;a;a=a[d])if(a.nodeType===1&&++f===b)break;return a},sibling:function(a,b){for(var d=[];a;a=a.nextSibling)a.nodeType===1&&a!==b&&d.push(a);return d}});var Ja=/ jQuery\d+="(?:\d+|null)"/g,V=/^\s+/,Ka=/(<([\w:]+)[^>]*?)\/>/g,hb=/^(?:area|br|col|embed|hr|img|input|link|meta|param)$/i,La=/<([\w:]+)/,ib=/"},F={option:[1,""],legend:[1,"
","
"],thead:[1,"","
"],tr:[2,"","
"],td:[3,"","
"],col:[2,"","
"],area:[1,"",""],_default:[0,"",""]};F.optgroup=F.option;F.tbody=F.tfoot=F.colgroup=F.caption=F.thead;F.th=F.td;if(!c.support.htmlSerialize)F._default=[1,"div
","
"];c.fn.extend({text:function(a){if(c.isFunction(a))return this.each(function(b){var d= +c(this);d.text(a.call(this,b,d.text()))});if(typeof a!=="object"&&a!==w)return this.empty().append((this[0]&&this[0].ownerDocument||s).createTextNode(a));return c.text(this)},wrapAll:function(a){if(c.isFunction(a))return this.each(function(d){c(this).wrapAll(a.call(this,d))});if(this[0]){var b=c(a,this[0].ownerDocument).eq(0).clone(true);this[0].parentNode&&b.insertBefore(this[0]);b.map(function(){for(var d=this;d.firstChild&&d.firstChild.nodeType===1;)d=d.firstChild;return d}).append(this)}return this}, +wrapInner:function(a){if(c.isFunction(a))return this.each(function(b){c(this).wrapInner(a.call(this,b))});return this.each(function(){var b=c(this),d=b.contents();d.length?d.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){c(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){c.nodeName(this,"body")||c(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.appendChild(a)})}, +prepend:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this)});else if(arguments.length){var a=c(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b, +this.nextSibling)});else if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,c(arguments[0]).toArray());return a}},remove:function(a,b){for(var d=0,f;(f=this[d])!=null;d++)if(!a||c.filter(a,[f]).length){if(!b&&f.nodeType===1){c.cleanData(f.getElementsByTagName("*"));c.cleanData([f])}f.parentNode&&f.parentNode.removeChild(f)}return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++)for(b.nodeType===1&&c.cleanData(b.getElementsByTagName("*"));b.firstChild;)b.removeChild(b.firstChild); +return this},clone:function(a){var b=this.map(function(){if(!c.support.noCloneEvent&&!c.isXMLDoc(this)){var d=this.outerHTML,f=this.ownerDocument;if(!d){d=f.createElement("div");d.appendChild(this.cloneNode(true));d=d.innerHTML}return c.clean([d.replace(Ja,"").replace(/=([^="'>\s]+\/)>/g,'="$1">').replace(V,"")],f)[0]}else return this.cloneNode(true)});if(a===true){ra(this,b);ra(this.find("*"),b.find("*"))}return b},html:function(a){if(a===w)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(Ja, +""):null;else if(typeof a==="string"&&!ta.test(a)&&(c.support.leadingWhitespace||!V.test(a))&&!F[(La.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Ka,Ma);try{for(var b=0,d=this.length;b0||e.cacheable||this.length>1?k.cloneNode(true):k)}o.length&&c.each(o,Qa)}return this}});c.fragments={};c.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){c.fn[a]=function(d){var f=[];d=c(d);var e=this.length===1&&this[0].parentNode;if(e&&e.nodeType===11&&e.childNodes.length===1&&d.length===1){d[b](this[0]); +return this}else{e=0;for(var j=d.length;e0?this.clone(true):this).get();c.fn[b].apply(c(d[e]),i);f=f.concat(i)}return this.pushStack(f,a,d.selector)}}});c.extend({clean:function(a,b,d,f){b=b||s;if(typeof b.createElement==="undefined")b=b.ownerDocument||b[0]&&b[0].ownerDocument||s;for(var e=[],j=0,i;(i=a[j])!=null;j++){if(typeof i==="number")i+="";if(i){if(typeof i==="string"&&!jb.test(i))i=b.createTextNode(i);else if(typeof i==="string"){i=i.replace(Ka,Ma);var o=(La.exec(i)||["", +""])[1].toLowerCase(),k=F[o]||F._default,n=k[0],r=b.createElement("div");for(r.innerHTML=k[1]+i+k[2];n--;)r=r.lastChild;if(!c.support.tbody){n=ib.test(i);o=o==="table"&&!n?r.firstChild&&r.firstChild.childNodes:k[1]===""&&!n?r.childNodes:[];for(k=o.length-1;k>=0;--k)c.nodeName(o[k],"tbody")&&!o[k].childNodes.length&&o[k].parentNode.removeChild(o[k])}!c.support.leadingWhitespace&&V.test(i)&&r.insertBefore(b.createTextNode(V.exec(i)[0]),r.firstChild);i=r.childNodes}if(i.nodeType)e.push(i);else e= +c.merge(e,i)}}if(d)for(j=0;e[j];j++)if(f&&c.nodeName(e[j],"script")&&(!e[j].type||e[j].type.toLowerCase()==="text/javascript"))f.push(e[j].parentNode?e[j].parentNode.removeChild(e[j]):e[j]);else{e[j].nodeType===1&&e.splice.apply(e,[j+1,0].concat(c.makeArray(e[j].getElementsByTagName("script"))));d.appendChild(e[j])}return e},cleanData:function(a){for(var b,d,f=c.cache,e=c.event.special,j=c.support.deleteExpando,i=0,o;(o=a[i])!=null;i++)if(d=o[c.expando]){b=f[d];if(b.events)for(var k in b.events)e[k]? +c.event.remove(o,k):Ca(o,k,b.handle);if(j)delete o[c.expando];else o.removeAttribute&&o.removeAttribute(c.expando);delete f[d]}}});var kb=/z-?index|font-?weight|opacity|zoom|line-?height/i,Na=/alpha\([^)]*\)/,Oa=/opacity=([^)]*)/,ha=/float/i,ia=/-([a-z])/ig,lb=/([A-Z])/g,mb=/^-?\d+(?:px)?$/i,nb=/^-?\d/,ob={position:"absolute",visibility:"hidden",display:"block"},pb=["Left","Right"],qb=["Top","Bottom"],rb=s.defaultView&&s.defaultView.getComputedStyle,Pa=c.support.cssFloat?"cssFloat":"styleFloat",ja= +function(a,b){return b.toUpperCase()};c.fn.css=function(a,b){return X(this,a,b,true,function(d,f,e){if(e===w)return c.curCSS(d,f);if(typeof e==="number"&&!kb.test(f))e+="px";c.style(d,f,e)})};c.extend({style:function(a,b,d){if(!a||a.nodeType===3||a.nodeType===8)return w;if((b==="width"||b==="height")&&parseFloat(d)<0)d=w;var f=a.style||a,e=d!==w;if(!c.support.opacity&&b==="opacity"){if(e){f.zoom=1;b=parseInt(d,10)+""==="NaN"?"":"alpha(opacity="+d*100+")";a=f.filter||c.curCSS(a,"filter")||"";f.filter= +Na.test(a)?a.replace(Na,b):b}return f.filter&&f.filter.indexOf("opacity=")>=0?parseFloat(Oa.exec(f.filter)[1])/100+"":""}if(ha.test(b))b=Pa;b=b.replace(ia,ja);if(e)f[b]=d;return f[b]},css:function(a,b,d,f){if(b==="width"||b==="height"){var e,j=b==="width"?pb:qb;function i(){e=b==="width"?a.offsetWidth:a.offsetHeight;f!=="border"&&c.each(j,function(){f||(e-=parseFloat(c.curCSS(a,"padding"+this,true))||0);if(f==="margin")e+=parseFloat(c.curCSS(a,"margin"+this,true))||0;else e-=parseFloat(c.curCSS(a, +"border"+this+"Width",true))||0})}a.offsetWidth!==0?i():c.swap(a,ob,i);return Math.max(0,Math.round(e))}return c.curCSS(a,b,d)},curCSS:function(a,b,d){var f,e=a.style;if(!c.support.opacity&&b==="opacity"&&a.currentStyle){f=Oa.test(a.currentStyle.filter||"")?parseFloat(RegExp.$1)/100+"":"";return f===""?"1":f}if(ha.test(b))b=Pa;if(!d&&e&&e[b])f=e[b];else if(rb){if(ha.test(b))b="float";b=b.replace(lb,"-$1").toLowerCase();e=a.ownerDocument.defaultView;if(!e)return null;if(a=e.getComputedStyle(a,null))f= +a.getPropertyValue(b);if(b==="opacity"&&f==="")f="1"}else if(a.currentStyle){d=b.replace(ia,ja);f=a.currentStyle[b]||a.currentStyle[d];if(!mb.test(f)&&nb.test(f)){b=e.left;var j=a.runtimeStyle.left;a.runtimeStyle.left=a.currentStyle.left;e.left=d==="fontSize"?"1em":f||0;f=e.pixelLeft+"px";e.left=b;a.runtimeStyle.left=j}}return f},swap:function(a,b,d){var f={};for(var e in b){f[e]=a.style[e];a.style[e]=b[e]}d.call(a);for(e in b)a.style[e]=f[e]}});if(c.expr&&c.expr.filters){c.expr.filters.hidden=function(a){var b= +a.offsetWidth,d=a.offsetHeight,f=a.nodeName.toLowerCase()==="tr";return b===0&&d===0&&!f?true:b>0&&d>0&&!f?false:c.curCSS(a,"display")==="none"};c.expr.filters.visible=function(a){return!c.expr.filters.hidden(a)}}var sb=J(),tb=//gi,ub=/select|textarea/i,vb=/color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week/i,N=/=\?(&|$)/,ka=/\?/,wb=/(\?|&)_=.*?(&|$)/,xb=/^(\w+:)?\/\/([^\/?#]+)/,yb=/%20/g,zb=c.fn.load;c.fn.extend({load:function(a,b,d){if(typeof a!== +"string")return zb.call(this,a);else if(!this.length)return this;var f=a.indexOf(" ");if(f>=0){var e=a.slice(f,a.length);a=a.slice(0,f)}f="GET";if(b)if(c.isFunction(b)){d=b;b=null}else if(typeof b==="object"){b=c.param(b,c.ajaxSettings.traditional);f="POST"}var j=this;c.ajax({url:a,type:f,dataType:"html",data:b,complete:function(i,o){if(o==="success"||o==="notmodified")j.html(e?c("
").append(i.responseText.replace(tb,"")).find(e):i.responseText);d&&j.each(d,[i.responseText,o,i])}});return this}, +serialize:function(){return c.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?c.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||ub.test(this.nodeName)||vb.test(this.type))}).map(function(a,b){a=c(this).val();return a==null?null:c.isArray(a)?c.map(a,function(d){return{name:b.name,value:d}}):{name:b.name,value:a}}).get()}});c.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "), +function(a,b){c.fn[b]=function(d){return this.bind(b,d)}});c.extend({get:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b=null}return c.ajax({type:"GET",url:a,data:b,success:d,dataType:f})},getScript:function(a,b){return c.get(a,null,b,"script")},getJSON:function(a,b,d){return c.get(a,b,d,"json")},post:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b={}}return c.ajax({type:"POST",url:a,data:b,success:d,dataType:f})},ajaxSetup:function(a){c.extend(c.ajaxSettings,a)},ajaxSettings:{url:location.href, +global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:A.XMLHttpRequest&&(A.location.protocol!=="file:"||!A.ActiveXObject)?function(){return new A.XMLHttpRequest}:function(){try{return new A.ActiveXObject("Microsoft.XMLHTTP")}catch(a){}},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},etag:{},ajax:function(a){function b(){e.success&& +e.success.call(k,o,i,x);e.global&&f("ajaxSuccess",[x,e])}function d(){e.complete&&e.complete.call(k,x,i);e.global&&f("ajaxComplete",[x,e]);e.global&&!--c.active&&c.event.trigger("ajaxStop")}function f(q,p){(e.context?c(e.context):c.event).trigger(q,p)}var e=c.extend(true,{},c.ajaxSettings,a),j,i,o,k=a&&a.context||e,n=e.type.toUpperCase();if(e.data&&e.processData&&typeof e.data!=="string")e.data=c.param(e.data,e.traditional);if(e.dataType==="jsonp"){if(n==="GET")N.test(e.url)||(e.url+=(ka.test(e.url)? +"&":"?")+(e.jsonp||"callback")+"=?");else if(!e.data||!N.test(e.data))e.data=(e.data?e.data+"&":"")+(e.jsonp||"callback")+"=?";e.dataType="json"}if(e.dataType==="json"&&(e.data&&N.test(e.data)||N.test(e.url))){j=e.jsonpCallback||"jsonp"+sb++;if(e.data)e.data=(e.data+"").replace(N,"="+j+"$1");e.url=e.url.replace(N,"="+j+"$1");e.dataType="script";A[j]=A[j]||function(q){o=q;b();d();A[j]=w;try{delete A[j]}catch(p){}z&&z.removeChild(C)}}if(e.dataType==="script"&&e.cache===null)e.cache=false;if(e.cache=== +false&&n==="GET"){var r=J(),u=e.url.replace(wb,"$1_="+r+"$2");e.url=u+(u===e.url?(ka.test(e.url)?"&":"?")+"_="+r:"")}if(e.data&&n==="GET")e.url+=(ka.test(e.url)?"&":"?")+e.data;e.global&&!c.active++&&c.event.trigger("ajaxStart");r=(r=xb.exec(e.url))&&(r[1]&&r[1]!==location.protocol||r[2]!==location.host);if(e.dataType==="script"&&n==="GET"&&r){var z=s.getElementsByTagName("head")[0]||s.documentElement,C=s.createElement("script");C.src=e.url;if(e.scriptCharset)C.charset=e.scriptCharset;if(!j){var B= +false;C.onload=C.onreadystatechange=function(){if(!B&&(!this.readyState||this.readyState==="loaded"||this.readyState==="complete")){B=true;b();d();C.onload=C.onreadystatechange=null;z&&C.parentNode&&z.removeChild(C)}}}z.insertBefore(C,z.firstChild);return w}var E=false,x=e.xhr();if(x){e.username?x.open(n,e.url,e.async,e.username,e.password):x.open(n,e.url,e.async);try{if(e.data||a&&a.contentType)x.setRequestHeader("Content-Type",e.contentType);if(e.ifModified){c.lastModified[e.url]&&x.setRequestHeader("If-Modified-Since", +c.lastModified[e.url]);c.etag[e.url]&&x.setRequestHeader("If-None-Match",c.etag[e.url])}r||x.setRequestHeader("X-Requested-With","XMLHttpRequest");x.setRequestHeader("Accept",e.dataType&&e.accepts[e.dataType]?e.accepts[e.dataType]+", */*":e.accepts._default)}catch(ga){}if(e.beforeSend&&e.beforeSend.call(k,x,e)===false){e.global&&!--c.active&&c.event.trigger("ajaxStop");x.abort();return false}e.global&&f("ajaxSend",[x,e]);var g=x.onreadystatechange=function(q){if(!x||x.readyState===0||q==="abort"){E|| +d();E=true;if(x)x.onreadystatechange=c.noop}else if(!E&&x&&(x.readyState===4||q==="timeout")){E=true;x.onreadystatechange=c.noop;i=q==="timeout"?"timeout":!c.httpSuccess(x)?"error":e.ifModified&&c.httpNotModified(x,e.url)?"notmodified":"success";var p;if(i==="success")try{o=c.httpData(x,e.dataType,e)}catch(v){i="parsererror";p=v}if(i==="success"||i==="notmodified")j||b();else c.handleError(e,x,i,p);d();q==="timeout"&&x.abort();if(e.async)x=null}};try{var h=x.abort;x.abort=function(){x&&h.call(x); +g("abort")}}catch(l){}e.async&&e.timeout>0&&setTimeout(function(){x&&!E&&g("timeout")},e.timeout);try{x.send(n==="POST"||n==="PUT"||n==="DELETE"?e.data:null)}catch(m){c.handleError(e,x,null,m);d()}e.async||g();return x}},handleError:function(a,b,d,f){if(a.error)a.error.call(a.context||a,b,d,f);if(a.global)(a.context?c(a.context):c.event).trigger("ajaxError",[b,a,f])},active:0,httpSuccess:function(a){try{return!a.status&&location.protocol==="file:"||a.status>=200&&a.status<300||a.status===304||a.status=== +1223||a.status===0}catch(b){}return false},httpNotModified:function(a,b){var d=a.getResponseHeader("Last-Modified"),f=a.getResponseHeader("Etag");if(d)c.lastModified[b]=d;if(f)c.etag[b]=f;return a.status===304||a.status===0},httpData:function(a,b,d){var f=a.getResponseHeader("content-type")||"",e=b==="xml"||!b&&f.indexOf("xml")>=0;a=e?a.responseXML:a.responseText;e&&a.documentElement.nodeName==="parsererror"&&c.error("parsererror");if(d&&d.dataFilter)a=d.dataFilter(a,b);if(typeof a==="string")if(b=== +"json"||!b&&f.indexOf("json")>=0)a=c.parseJSON(a);else if(b==="script"||!b&&f.indexOf("javascript")>=0)c.globalEval(a);return a},param:function(a,b){function d(i,o){if(c.isArray(o))c.each(o,function(k,n){b||/\[\]$/.test(i)?f(i,n):d(i+"["+(typeof n==="object"||c.isArray(n)?k:"")+"]",n)});else!b&&o!=null&&typeof o==="object"?c.each(o,function(k,n){d(i+"["+k+"]",n)}):f(i,o)}function f(i,o){o=c.isFunction(o)?o():o;e[e.length]=encodeURIComponent(i)+"="+encodeURIComponent(o)}var e=[];if(b===w)b=c.ajaxSettings.traditional; +if(c.isArray(a)||a.jquery)c.each(a,function(){f(this.name,this.value)});else for(var j in a)d(j,a[j]);return e.join("&").replace(yb,"+")}});var la={},Ab=/toggle|show|hide/,Bb=/^([+-]=)?([\d+-.]+)(.*)$/,W,va=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];c.fn.extend({show:function(a,b){if(a||a===0)return this.animate(K("show",3),a,b);else{a=0;for(b=this.length;a").appendTo("body");f=e.css("display");if(f==="none")f="block";e.remove();la[d]=f}c.data(this[a],"olddisplay",f)}}a=0;for(b=this.length;a=0;f--)if(d[f].elem===this){b&&d[f](true);d.splice(f,1)}});b||this.dequeue();return this}});c.each({slideDown:K("show",1),slideUp:K("hide",1),slideToggle:K("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(a,b){c.fn[a]=function(d,f){return this.animate(b,d,f)}});c.extend({speed:function(a,b,d){var f=a&&typeof a==="object"?a:{complete:d||!d&&b||c.isFunction(a)&&a,duration:a,easing:d&&b||b&&!c.isFunction(b)&&b};f.duration=c.fx.off?0:typeof f.duration=== +"number"?f.duration:c.fx.speeds[f.duration]||c.fx.speeds._default;f.old=f.complete;f.complete=function(){f.queue!==false&&c(this).dequeue();c.isFunction(f.old)&&f.old.call(this)};return f},easing:{linear:function(a,b,d,f){return d+f*a},swing:function(a,b,d,f){return(-Math.cos(a*Math.PI)/2+0.5)*f+d}},timers:[],fx:function(a,b,d){this.options=b;this.elem=a;this.prop=d;if(!b.orig)b.orig={}}});c.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this);(c.fx.step[this.prop]|| +c.fx.step._default)(this);if((this.prop==="height"||this.prop==="width")&&this.elem.style)this.elem.style.display="block"},cur:function(a){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];return(a=parseFloat(c.css(this.elem,this.prop,a)))&&a>-10000?a:parseFloat(c.curCSS(this.elem,this.prop))||0},custom:function(a,b,d){function f(j){return e.step(j)}this.startTime=J();this.start=a;this.end=b;this.unit=d||this.unit||"px";this.now=this.start; +this.pos=this.state=0;var e=this;f.elem=this.elem;if(f()&&c.timers.push(f)&&!W)W=setInterval(c.fx.tick,13)},show:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.show=true;this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur());c(this.elem).show()},hide:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.hide=true;this.custom(this.cur(),0)},step:function(a){var b=J(),d=true;if(a||b>=this.options.duration+this.startTime){this.now= +this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;for(var f in this.options.curAnim)if(this.options.curAnim[f]!==true)d=false;if(d){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;a=c.data(this.elem,"olddisplay");this.elem.style.display=a?a:this.options.display;if(c.css(this.elem,"display")==="none")this.elem.style.display="block"}this.options.hide&&c(this.elem).hide();if(this.options.hide||this.options.show)for(var e in this.options.curAnim)c.style(this.elem, +e,this.options.orig[e]);this.options.complete.call(this.elem)}return false}else{e=b-this.startTime;this.state=e/this.options.duration;a=this.options.easing||(c.easing.swing?"swing":"linear");this.pos=c.easing[this.options.specialEasing&&this.options.specialEasing[this.prop]||a](this.state,e,0,1,this.options.duration);this.now=this.start+(this.end-this.start)*this.pos;this.update()}return true}};c.extend(c.fx,{tick:function(){for(var a=c.timers,b=0;b
"; +a.insertBefore(b,a.firstChild);d=b.firstChild;f=d.firstChild;e=d.nextSibling.firstChild.firstChild;this.doesNotAddBorder=f.offsetTop!==5;this.doesAddBorderForTableAndCells=e.offsetTop===5;f.style.position="fixed";f.style.top="20px";this.supportsFixedPosition=f.offsetTop===20||f.offsetTop===15;f.style.position=f.style.top="";d.style.overflow="hidden";d.style.position="relative";this.subtractsBorderForOverflowNotVisible=f.offsetTop===-5;this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==j;a.removeChild(b); +c.offset.initialize=c.noop},bodyOffset:function(a){var b=a.offsetTop,d=a.offsetLeft;c.offset.initialize();if(c.offset.doesNotIncludeMarginInBodyOffset){b+=parseFloat(c.curCSS(a,"marginTop",true))||0;d+=parseFloat(c.curCSS(a,"marginLeft",true))||0}return{top:b,left:d}},setOffset:function(a,b,d){if(/static/.test(c.curCSS(a,"position")))a.style.position="relative";var f=c(a),e=f.offset(),j=parseInt(c.curCSS(a,"top",true),10)||0,i=parseInt(c.curCSS(a,"left",true),10)||0;if(c.isFunction(b))b=b.call(a, +d,e);d={top:b.top-e.top+j,left:b.left-e.left+i};"using"in b?b.using.call(a,d):f.css(d)}};c.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),d=this.offset(),f=/^body|html$/i.test(b[0].nodeName)?{top:0,left:0}:b.offset();d.top-=parseFloat(c.curCSS(a,"marginTop",true))||0;d.left-=parseFloat(c.curCSS(a,"marginLeft",true))||0;f.top+=parseFloat(c.curCSS(b[0],"borderTopWidth",true))||0;f.left+=parseFloat(c.curCSS(b[0],"borderLeftWidth",true))||0;return{top:d.top- +f.top,left:d.left-f.left}},offsetParent:function(){return this.map(function(){for(var a=this.offsetParent||s.body;a&&!/^body|html$/i.test(a.nodeName)&&c.css(a,"position")==="static";)a=a.offsetParent;return a})}});c.each(["Left","Top"],function(a,b){var d="scroll"+b;c.fn[d]=function(f){var e=this[0],j;if(!e)return null;if(f!==w)return this.each(function(){if(j=wa(this))j.scrollTo(!a?f:c(j).scrollLeft(),a?f:c(j).scrollTop());else this[d]=f});else return(j=wa(e))?"pageXOffset"in j?j[a?"pageYOffset": +"pageXOffset"]:c.support.boxModel&&j.document.documentElement[d]||j.document.body[d]:e[d]}});c.each(["Height","Width"],function(a,b){var d=b.toLowerCase();c.fn["inner"+b]=function(){return this[0]?c.css(this[0],d,false,"padding"):null};c.fn["outer"+b]=function(f){return this[0]?c.css(this[0],d,false,f?"margin":"border"):null};c.fn[d]=function(f){var e=this[0];if(!e)return f==null?null:this;if(c.isFunction(f))return this.each(function(j){var i=c(this);i[d](f.call(this,j,i[d]()))});return"scrollTo"in +e&&e.document?e.document.compatMode==="CSS1Compat"&&e.document.documentElement["client"+b]||e.document.body["client"+b]:e.nodeType===9?Math.max(e.documentElement["client"+b],e.body["scroll"+b],e.documentElement["scroll"+b],e.body["offset"+b],e.documentElement["offset"+b]):f===w?c.css(e,d):this.css(d,typeof f==="string"?f:f+"px")}});A.jQuery=A.$=c})(window); diff --git a/web/js/jquery.js b/web/js/jquery.js new file mode 100644 index 0000000..f7f4227 --- /dev/null +++ b/web/js/jquery.js @@ -0,0 +1,9111 @@ +/*! + * jQuery JavaScript Library v2.1.0 + * http://jquery.com/ + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * + * Copyright 2005, 2014 jQuery Foundation, Inc. and other contributors + * Released under the MIT license + * http://jquery.org/license + * + * Date: 2014-01-23T21:10Z + */ + +(function( global, factory ) { + + if ( typeof module === "object" && typeof module.exports === "object" ) { + // For CommonJS and CommonJS-like environments where a proper window is present, + // execute the factory and get jQuery + // For environments that do not inherently posses a window with a document + // (such as Node.js), expose a jQuery-making factory as module.exports + // This accentuates the need for the creation of a real window + // e.g. var jQuery = require("jquery")(window); + // See ticket #14549 for more info + module.exports = global.document ? + factory( global, true ) : + function( w ) { + if ( !w.document ) { + throw new Error( "jQuery requires a window with a document" ); + } + return factory( w ); + }; + } else { + factory( global ); + } + +// Pass this if window is not defined yet +}(typeof window !== "undefined" ? window : this, function( window, noGlobal ) { + +// Can't do this because several apps including ASP.NET trace +// the stack via arguments.caller.callee and Firefox dies if +// you try to trace through "use strict" call chains. (#13335) +// Support: Firefox 18+ +// + +var arr = []; + +var slice = arr.slice; + +var concat = arr.concat; + +var push = arr.push; + +var indexOf = arr.indexOf; + +var class2type = {}; + +var toString = class2type.toString; + +var hasOwn = class2type.hasOwnProperty; + +var trim = "".trim; + +var support = {}; + + + +var + // Use the correct document accordingly with window argument (sandbox) + document = window.document, + + version = "2.1.0", + + // Define a local copy of jQuery + jQuery = function( selector, context ) { + // The jQuery object is actually just the init constructor 'enhanced' + // Need init if jQuery is called (just allow error to be thrown if not included) + return new jQuery.fn.init( selector, context ); + }, + + // Matches dashed string for camelizing + rmsPrefix = /^-ms-/, + rdashAlpha = /-([\da-z])/gi, + + // Used by jQuery.camelCase as callback to replace() + fcamelCase = function( all, letter ) { + return letter.toUpperCase(); + }; + +jQuery.fn = jQuery.prototype = { + // The current version of jQuery being used + jquery: version, + + constructor: jQuery, + + // Start with an empty selector + selector: "", + + // The default length of a jQuery object is 0 + length: 0, + + toArray: function() { + return slice.call( this ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + return num != null ? + + // Return a 'clean' array + ( num < 0 ? this[ num + this.length ] : this[ num ] ) : + + // Return just the object + slice.call( this ); + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems ) { + + // Build a new jQuery matched element set + var ret = jQuery.merge( this.constructor(), elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + ret.context = this.context; + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + // (You can seed the arguments with an array of args, but this is + // only used internally.) + each: function( callback, args ) { + return jQuery.each( this, callback, args ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map(this, function( elem, i ) { + return callback.call( elem, i, elem ); + })); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ) ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + eq: function( i ) { + var len = this.length, + j = +i + ( i < 0 ? len : 0 ); + return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] ); + }, + + end: function() { + return this.prevObject || this.constructor(null); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: arr.sort, + splice: arr.splice +}; + +jQuery.extend = jQuery.fn.extend = function() { + var options, name, src, copy, copyIsArray, clone, + target = arguments[0] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + + // skip the boolean and the target + target = arguments[ i ] || {}; + i++; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !jQuery.isFunction(target) ) { + target = {}; + } + + // extend jQuery itself if only one argument is passed + if ( i === length ) { + target = this; + i--; + } + + for ( ; i < length; i++ ) { + // Only deal with non-null/undefined values + if ( (options = arguments[ i ]) != null ) { + // Extend the base object + for ( name in options ) { + src = target[ name ]; + copy = options[ name ]; + + // Prevent never-ending loop + if ( target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { + if ( copyIsArray ) { + copyIsArray = false; + clone = src && jQuery.isArray(src) ? src : []; + + } else { + clone = src && jQuery.isPlainObject(src) ? src : {}; + } + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend({ + // Unique for each copy of jQuery on the page + expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), + + // Assume jQuery is ready without the ready module + isReady: true, + + error: function( msg ) { + throw new Error( msg ); + }, + + noop: function() {}, + + // See test/unit/core.js for details concerning isFunction. + // Since version 1.3, DOM methods and functions like alert + // aren't supported. They return false on IE (#2968). + isFunction: function( obj ) { + return jQuery.type(obj) === "function"; + }, + + isArray: Array.isArray, + + isWindow: function( obj ) { + return obj != null && obj === obj.window; + }, + + isNumeric: function( obj ) { + // parseFloat NaNs numeric-cast false positives (null|true|false|"") + // ...but misinterprets leading-number strings, particularly hex literals ("0x...") + // subtraction forces infinities to NaN + return obj - parseFloat( obj ) >= 0; + }, + + isPlainObject: function( obj ) { + // Not plain objects: + // - Any object or value whose internal [[Class]] property is not "[object Object]" + // - DOM nodes + // - window + if ( jQuery.type( obj ) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) { + return false; + } + + // Support: Firefox <20 + // The try/catch suppresses exceptions thrown when attempting to access + // the "constructor" property of certain host objects, ie. |window.location| + // https://bugzilla.mozilla.org/show_bug.cgi?id=814622 + try { + if ( obj.constructor && + !hasOwn.call( obj.constructor.prototype, "isPrototypeOf" ) ) { + return false; + } + } catch ( e ) { + return false; + } + + // If the function hasn't returned already, we're confident that + // |obj| is a plain object, created by {} or constructed with new Object + return true; + }, + + isEmptyObject: function( obj ) { + var name; + for ( name in obj ) { + return false; + } + return true; + }, + + type: function( obj ) { + if ( obj == null ) { + return obj + ""; + } + // Support: Android < 4.0, iOS < 6 (functionish RegExp) + return typeof obj === "object" || typeof obj === "function" ? + class2type[ toString.call(obj) ] || "object" : + typeof obj; + }, + + // Evaluates a script in a global context + globalEval: function( code ) { + var script, + indirect = eval; + + code = jQuery.trim( code ); + + if ( code ) { + // If the code includes a valid, prologue position + // strict mode pragma, execute code by injecting a + // script tag into the document. + if ( code.indexOf("use strict") === 1 ) { + script = document.createElement("script"); + script.text = code; + document.head.appendChild( script ).parentNode.removeChild( script ); + } else { + // Otherwise, avoid the DOM node creation, insertion + // and removal by using an indirect global eval + indirect( code ); + } + } + }, + + // Convert dashed to camelCase; used by the css and data modules + // Microsoft forgot to hump their vendor prefix (#9572) + camelCase: function( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); + }, + + nodeName: function( elem, name ) { + return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); + }, + + // args is for internal usage only + each: function( obj, callback, args ) { + var value, + i = 0, + length = obj.length, + isArray = isArraylike( obj ); + + if ( args ) { + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback.apply( obj[ i ], args ); + + if ( value === false ) { + break; + } + } + } else { + for ( i in obj ) { + value = callback.apply( obj[ i ], args ); + + if ( value === false ) { + break; + } + } + } + + // A special, fast, case for the most common use of each + } else { + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback.call( obj[ i ], i, obj[ i ] ); + + if ( value === false ) { + break; + } + } + } else { + for ( i in obj ) { + value = callback.call( obj[ i ], i, obj[ i ] ); + + if ( value === false ) { + break; + } + } + } + } + + return obj; + }, + + trim: function( text ) { + return text == null ? "" : trim.call( text ); + }, + + // results is for internal usage only + makeArray: function( arr, results ) { + var ret = results || []; + + if ( arr != null ) { + if ( isArraylike( Object(arr) ) ) { + jQuery.merge( ret, + typeof arr === "string" ? + [ arr ] : arr + ); + } else { + push.call( ret, arr ); + } + } + + return ret; + }, + + inArray: function( elem, arr, i ) { + return arr == null ? -1 : indexOf.call( arr, elem, i ); + }, + + merge: function( first, second ) { + var len = +second.length, + j = 0, + i = first.length; + + for ( ; j < len; j++ ) { + first[ i++ ] = second[ j ]; + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, invert ) { + var callbackInverse, + matches = [], + i = 0, + length = elems.length, + callbackExpect = !invert; + + // Go through the array, only saving the items + // that pass the validator function + for ( ; i < length; i++ ) { + callbackInverse = !callback( elems[ i ], i ); + if ( callbackInverse !== callbackExpect ) { + matches.push( elems[ i ] ); + } + } + + return matches; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var value, + i = 0, + length = elems.length, + isArray = isArraylike( elems ), + ret = []; + + // Go through the array, translating each of the items to their new values + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + + // Go through every key on the object, + } else { + for ( i in elems ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + } + + // Flatten any nested arrays + return concat.apply( [], ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // Bind a function to a context, optionally partially applying any + // arguments. + proxy: function( fn, context ) { + var tmp, args, proxy; + + if ( typeof context === "string" ) { + tmp = fn[ context ]; + context = fn; + fn = tmp; + } + + // Quick check to determine if target is callable, in the spec + // this throws a TypeError, but we will just return undefined. + if ( !jQuery.isFunction( fn ) ) { + return undefined; + } + + // Simulated bind + args = slice.call( arguments, 2 ); + proxy = function() { + return fn.apply( context || this, args.concat( slice.call( arguments ) ) ); + }; + + // Set the guid of unique handler to the same of original handler, so it can be removed + proxy.guid = fn.guid = fn.guid || jQuery.guid++; + + return proxy; + }, + + now: Date.now, + + // jQuery.support is not used in Core but other projects attach their + // properties to it so it needs to exist. + support: support +}); + +// Populate the class2type map +jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +}); + +function isArraylike( obj ) { + var length = obj.length, + type = jQuery.type( obj ); + + if ( type === "function" || jQuery.isWindow( obj ) ) { + return false; + } + + if ( obj.nodeType === 1 && length ) { + return true; + } + + return type === "array" || length === 0 || + typeof length === "number" && length > 0 && ( length - 1 ) in obj; +} +var Sizzle = +/*! + * Sizzle CSS Selector Engine v1.10.16 + * http://sizzlejs.com/ + * + * Copyright 2013 jQuery Foundation, Inc. and other contributors + * Released under the MIT license + * http://jquery.org/license + * + * Date: 2014-01-13 + */ +(function( window ) { + +var i, + support, + Expr, + getText, + isXML, + compile, + outermostContext, + sortInput, + hasDuplicate, + + // Local document vars + setDocument, + document, + docElem, + documentIsHTML, + rbuggyQSA, + rbuggyMatches, + matches, + contains, + + // Instance-specific data + expando = "sizzle" + -(new Date()), + preferredDoc = window.document, + dirruns = 0, + done = 0, + classCache = createCache(), + tokenCache = createCache(), + compilerCache = createCache(), + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + } + return 0; + }, + + // General-purpose constants + strundefined = typeof undefined, + MAX_NEGATIVE = 1 << 31, + + // Instance methods + hasOwn = ({}).hasOwnProperty, + arr = [], + pop = arr.pop, + push_native = arr.push, + push = arr.push, + slice = arr.slice, + // Use a stripped-down indexOf if we can't use a native one + indexOf = arr.indexOf || function( elem ) { + var i = 0, + len = this.length; + for ( ; i < len; i++ ) { + if ( this[i] === elem ) { + return i; + } + } + return -1; + }, + + booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped", + + // Regular expressions + + // Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace + whitespace = "[\\x20\\t\\r\\n\\f]", + // http://www.w3.org/TR/css3-syntax/#characters + characterEncoding = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+", + + // Loosely modeled on CSS identifier characters + // An unquoted value should be a CSS identifier http://www.w3.org/TR/css3-selectors/#attribute-selectors + // Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier + identifier = characterEncoding.replace( "w", "w#" ), + + // Acceptable operators http://www.w3.org/TR/selectors/#attribute-selectors + attributes = "\\[" + whitespace + "*(" + characterEncoding + ")" + whitespace + + "*(?:([*^$|!~]?=)" + whitespace + "*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|(" + identifier + ")|)|)" + whitespace + "*\\]", + + // Prefer arguments quoted, + // then not containing pseudos/brackets, + // then attribute selectors/non-parenthetical expressions, + // then anything else + // These preferences are here to reduce the number of selectors + // needing tokenize in the PSEUDO preFilter + pseudos = ":(" + characterEncoding + ")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|" + attributes.replace( 3, 8 ) + ")*)|.*)\\)|)", + + // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter + rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), + + rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), + rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ), + + rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ), + + rpseudo = new RegExp( pseudos ), + ridentifier = new RegExp( "^" + identifier + "$" ), + + matchExpr = { + "ID": new RegExp( "^#(" + characterEncoding + ")" ), + "CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ), + "TAG": new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")" ), + "ATTR": new RegExp( "^" + attributes ), + "PSEUDO": new RegExp( "^" + pseudos ), + "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), + // For use in libraries implementing .is() + // We use this for POS matching in `select` + "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + + whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) + }, + + rinputs = /^(?:input|select|textarea|button)$/i, + rheader = /^h\d$/i, + + rnative = /^[^{]+\{\s*\[native \w/, + + // Easily-parseable/retrievable ID or TAG or CLASS selectors + rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, + + rsibling = /[+~]/, + rescape = /'|\\/g, + + // CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters + runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ), + funescape = function( _, escaped, escapedWhitespace ) { + var high = "0x" + escaped - 0x10000; + // NaN means non-codepoint + // Support: Firefox + // Workaround erroneous numeric interpretation of +"0x" + return high !== high || escapedWhitespace ? + escaped : + high < 0 ? + // BMP codepoint + String.fromCharCode( high + 0x10000 ) : + // Supplemental Plane codepoint (surrogate pair) + String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); + }; + +// Optimize for push.apply( _, NodeList ) +try { + push.apply( + (arr = slice.call( preferredDoc.childNodes )), + preferredDoc.childNodes + ); + // Support: Android<4.0 + // Detect silently failing push.apply + arr[ preferredDoc.childNodes.length ].nodeType; +} catch ( e ) { + push = { apply: arr.length ? + + // Leverage slice if possible + function( target, els ) { + push_native.apply( target, slice.call(els) ); + } : + + // Support: IE<9 + // Otherwise append directly + function( target, els ) { + var j = target.length, + i = 0; + // Can't trust NodeList.length + while ( (target[j++] = els[i++]) ) {} + target.length = j - 1; + } + }; +} + +function Sizzle( selector, context, results, seed ) { + var match, elem, m, nodeType, + // QSA vars + i, groups, old, nid, newContext, newSelector; + + if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { + setDocument( context ); + } + + context = context || document; + results = results || []; + + if ( !selector || typeof selector !== "string" ) { + return results; + } + + if ( (nodeType = context.nodeType) !== 1 && nodeType !== 9 ) { + return []; + } + + if ( documentIsHTML && !seed ) { + + // Shortcuts + if ( (match = rquickExpr.exec( selector )) ) { + // Speed-up: Sizzle("#ID") + if ( (m = match[1]) ) { + if ( nodeType === 9 ) { + elem = context.getElementById( m ); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document (jQuery #6963) + if ( elem && elem.parentNode ) { + // Handle the case where IE, Opera, and Webkit return items + // by name instead of ID + if ( elem.id === m ) { + results.push( elem ); + return results; + } + } else { + return results; + } + } else { + // Context is not a document + if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) && + contains( context, elem ) && elem.id === m ) { + results.push( elem ); + return results; + } + } + + // Speed-up: Sizzle("TAG") + } else if ( match[2] ) { + push.apply( results, context.getElementsByTagName( selector ) ); + return results; + + // Speed-up: Sizzle(".CLASS") + } else if ( (m = match[3]) && support.getElementsByClassName && context.getElementsByClassName ) { + push.apply( results, context.getElementsByClassName( m ) ); + return results; + } + } + + // QSA path + if ( support.qsa && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) { + nid = old = expando; + newContext = context; + newSelector = nodeType === 9 && selector; + + // qSA works strangely on Element-rooted queries + // We can work around this by specifying an extra ID on the root + // and working up from there (Thanks to Andrew Dupont for the technique) + // IE 8 doesn't work on object elements + if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { + groups = tokenize( selector ); + + if ( (old = context.getAttribute("id")) ) { + nid = old.replace( rescape, "\\$&" ); + } else { + context.setAttribute( "id", nid ); + } + nid = "[id='" + nid + "'] "; + + i = groups.length; + while ( i-- ) { + groups[i] = nid + toSelector( groups[i] ); + } + newContext = rsibling.test( selector ) && testContext( context.parentNode ) || context; + newSelector = groups.join(","); + } + + if ( newSelector ) { + try { + push.apply( results, + newContext.querySelectorAll( newSelector ) + ); + return results; + } catch(qsaError) { + } finally { + if ( !old ) { + context.removeAttribute("id"); + } + } + } + } + } + + // All others + return select( selector.replace( rtrim, "$1" ), context, results, seed ); +} + +/** + * Create key-value caches of limited size + * @returns {Function(string, Object)} Returns the Object data after storing it on itself with + * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) + * deleting the oldest entry + */ +function createCache() { + var keys = []; + + function cache( key, value ) { + // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) + if ( keys.push( key + " " ) > Expr.cacheLength ) { + // Only keep the most recent entries + delete cache[ keys.shift() ]; + } + return (cache[ key + " " ] = value); + } + return cache; +} + +/** + * Mark a function for special use by Sizzle + * @param {Function} fn The function to mark + */ +function markFunction( fn ) { + fn[ expando ] = true; + return fn; +} + +/** + * Support testing using an element + * @param {Function} fn Passed the created div and expects a boolean result + */ +function assert( fn ) { + var div = document.createElement("div"); + + try { + return !!fn( div ); + } catch (e) { + return false; + } finally { + // Remove from its parent by default + if ( div.parentNode ) { + div.parentNode.removeChild( div ); + } + // release memory in IE + div = null; + } +} + +/** + * Adds the same handler for all of the specified attrs + * @param {String} attrs Pipe-separated list of attributes + * @param {Function} handler The method that will be applied + */ +function addHandle( attrs, handler ) { + var arr = attrs.split("|"), + i = attrs.length; + + while ( i-- ) { + Expr.attrHandle[ arr[i] ] = handler; + } +} + +/** + * Checks document order of two siblings + * @param {Element} a + * @param {Element} b + * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b + */ +function siblingCheck( a, b ) { + var cur = b && a, + diff = cur && a.nodeType === 1 && b.nodeType === 1 && + ( ~b.sourceIndex || MAX_NEGATIVE ) - + ( ~a.sourceIndex || MAX_NEGATIVE ); + + // Use IE sourceIndex if available on both nodes + if ( diff ) { + return diff; + } + + // Check if b follows a + if ( cur ) { + while ( (cur = cur.nextSibling) ) { + if ( cur === b ) { + return -1; + } + } + } + + return a ? 1 : -1; +} + +/** + * Returns a function to use in pseudos for input types + * @param {String} type + */ +function createInputPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for buttons + * @param {String} type + */ +function createButtonPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for positionals + * @param {Function} fn + */ +function createPositionalPseudo( fn ) { + return markFunction(function( argument ) { + argument = +argument; + return markFunction(function( seed, matches ) { + var j, + matchIndexes = fn( [], seed.length, argument ), + i = matchIndexes.length; + + // Match elements found at the specified indexes + while ( i-- ) { + if ( seed[ (j = matchIndexes[i]) ] ) { + seed[j] = !(matches[j] = seed[j]); + } + } + }); + }); +} + +/** + * Checks a node for validity as a Sizzle context + * @param {Element|Object=} context + * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value + */ +function testContext( context ) { + return context && typeof context.getElementsByTagName !== strundefined && context; +} + +// Expose support vars for convenience +support = Sizzle.support = {}; + +/** + * Detects XML nodes + * @param {Element|Object} elem An element or a document + * @returns {Boolean} True iff elem is a non-HTML XML node + */ +isXML = Sizzle.isXML = function( elem ) { + // documentElement is verified for cases where it doesn't yet exist + // (such as loading iframes in IE - #4833) + var documentElement = elem && (elem.ownerDocument || elem).documentElement; + return documentElement ? documentElement.nodeName !== "HTML" : false; +}; + +/** + * Sets document-related variables once based on the current document + * @param {Element|Object} [doc] An element or document object to use to set the document + * @returns {Object} Returns the current document + */ +setDocument = Sizzle.setDocument = function( node ) { + var hasCompare, + doc = node ? node.ownerDocument || node : preferredDoc, + parent = doc.defaultView; + + // If no document and documentElement is available, return + if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) { + return document; + } + + // Set our document + document = doc; + docElem = doc.documentElement; + + // Support tests + documentIsHTML = !isXML( doc ); + + // Support: IE>8 + // If iframe document is assigned to "document" variable and if iframe has been reloaded, + // IE will throw "permission denied" error when accessing "document" variable, see jQuery #13936 + // IE6-8 do not support the defaultView property so parent will be undefined + if ( parent && parent !== parent.top ) { + // IE11 does not have attachEvent, so all must suffer + if ( parent.addEventListener ) { + parent.addEventListener( "unload", function() { + setDocument(); + }, false ); + } else if ( parent.attachEvent ) { + parent.attachEvent( "onunload", function() { + setDocument(); + }); + } + } + + /* Attributes + ---------------------------------------------------------------------- */ + + // Support: IE<8 + // Verify that getAttribute really returns attributes and not properties (excepting IE8 booleans) + support.attributes = assert(function( div ) { + div.className = "i"; + return !div.getAttribute("className"); + }); + + /* getElement(s)By* + ---------------------------------------------------------------------- */ + + // Check if getElementsByTagName("*") returns only elements + support.getElementsByTagName = assert(function( div ) { + div.appendChild( doc.createComment("") ); + return !div.getElementsByTagName("*").length; + }); + + // Check if getElementsByClassName can be trusted + support.getElementsByClassName = rnative.test( doc.getElementsByClassName ) && assert(function( div ) { + div.innerHTML = "
"; + + // Support: Safari<4 + // Catch class over-caching + div.firstChild.className = "i"; + // Support: Opera<10 + // Catch gEBCN failure to find non-leading classes + return div.getElementsByClassName("i").length === 2; + }); + + // Support: IE<10 + // Check if getElementById returns elements by name + // The broken getElementById methods don't pick up programatically-set names, + // so use a roundabout getElementsByName test + support.getById = assert(function( div ) { + docElem.appendChild( div ).id = expando; + return !doc.getElementsByName || !doc.getElementsByName( expando ).length; + }); + + // ID find and filter + if ( support.getById ) { + Expr.find["ID"] = function( id, context ) { + if ( typeof context.getElementById !== strundefined && documentIsHTML ) { + var m = context.getElementById( id ); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + return m && m.parentNode ? [m] : []; + } + }; + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + return elem.getAttribute("id") === attrId; + }; + }; + } else { + // Support: IE6/7 + // getElementById is not reliable as a find shortcut + delete Expr.find["ID"]; + + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode("id"); + return node && node.value === attrId; + }; + }; + } + + // Tag + Expr.find["TAG"] = support.getElementsByTagName ? + function( tag, context ) { + if ( typeof context.getElementsByTagName !== strundefined ) { + return context.getElementsByTagName( tag ); + } + } : + function( tag, context ) { + var elem, + tmp = [], + i = 0, + results = context.getElementsByTagName( tag ); + + // Filter out possible comments + if ( tag === "*" ) { + while ( (elem = results[i++]) ) { + if ( elem.nodeType === 1 ) { + tmp.push( elem ); + } + } + + return tmp; + } + return results; + }; + + // Class + Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) { + if ( typeof context.getElementsByClassName !== strundefined && documentIsHTML ) { + return context.getElementsByClassName( className ); + } + }; + + /* QSA/matchesSelector + ---------------------------------------------------------------------- */ + + // QSA and matchesSelector support + + // matchesSelector(:active) reports false when true (IE9/Opera 11.5) + rbuggyMatches = []; + + // qSa(:focus) reports false when true (Chrome 21) + // We allow this because of a bug in IE8/9 that throws an error + // whenever `document.activeElement` is accessed on an iframe + // So, we allow :focus to pass through QSA all the time to avoid the IE error + // See http://bugs.jquery.com/ticket/13378 + rbuggyQSA = []; + + if ( (support.qsa = rnative.test( doc.querySelectorAll )) ) { + // Build QSA regex + // Regex strategy adopted from Diego Perini + assert(function( div ) { + // Select is set to empty string on purpose + // This is to test IE's treatment of not explicitly + // setting a boolean content attribute, + // since its presence should be enough + // http://bugs.jquery.com/ticket/12359 + div.innerHTML = ""; + + // Support: IE8, Opera 10-12 + // Nothing should be selected when empty strings follow ^= or $= or *= + if ( div.querySelectorAll("[t^='']").length ) { + rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); + } + + // Support: IE8 + // Boolean attributes and "value" are not treated correctly + if ( !div.querySelectorAll("[selected]").length ) { + rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); + } + + // Webkit/Opera - :checked should return selected option elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + // IE8 throws error here and will not see later tests + if ( !div.querySelectorAll(":checked").length ) { + rbuggyQSA.push(":checked"); + } + }); + + assert(function( div ) { + // Support: Windows 8 Native Apps + // The type and name attributes are restricted during .innerHTML assignment + var input = doc.createElement("input"); + input.setAttribute( "type", "hidden" ); + div.appendChild( input ).setAttribute( "name", "D" ); + + // Support: IE8 + // Enforce case-sensitivity of name attribute + if ( div.querySelectorAll("[name=d]").length ) { + rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); + } + + // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) + // IE8 throws error here and will not see later tests + if ( !div.querySelectorAll(":enabled").length ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Opera 10-11 does not throw on post-comma invalid pseudos + div.querySelectorAll("*,:x"); + rbuggyQSA.push(",.*:"); + }); + } + + if ( (support.matchesSelector = rnative.test( (matches = docElem.webkitMatchesSelector || + docElem.mozMatchesSelector || + docElem.oMatchesSelector || + docElem.msMatchesSelector) )) ) { + + assert(function( div ) { + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9) + support.disconnectedMatch = matches.call( div, "div" ); + + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( div, "[s!='']:x" ); + rbuggyMatches.push( "!=", pseudos ); + }); + } + + rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") ); + rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") ); + + /* Contains + ---------------------------------------------------------------------- */ + hasCompare = rnative.test( docElem.compareDocumentPosition ); + + // Element contains another + // Purposefully does not implement inclusive descendent + // As in, an element does not contain itself + contains = hasCompare || rnative.test( docElem.contains ) ? + function( a, b ) { + var adown = a.nodeType === 9 ? a.documentElement : a, + bup = b && b.parentNode; + return a === bup || !!( bup && bup.nodeType === 1 && ( + adown.contains ? + adown.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + )); + } : + function( a, b ) { + if ( b ) { + while ( (b = b.parentNode) ) { + if ( b === a ) { + return true; + } + } + } + return false; + }; + + /* Sorting + ---------------------------------------------------------------------- */ + + // Document order sorting + sortOrder = hasCompare ? + function( a, b ) { + + // Flag for duplicate removal + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + // Sort on method existence if only one input has compareDocumentPosition + var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; + if ( compare ) { + return compare; + } + + // Calculate position if both inputs belong to the same document + compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ? + a.compareDocumentPosition( b ) : + + // Otherwise we know they are disconnected + 1; + + // Disconnected nodes + if ( compare & 1 || + (!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) { + + // Choose the first element that is related to our preferred document + if ( a === doc || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) { + return -1; + } + if ( b === doc || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) { + return 1; + } + + // Maintain original order + return sortInput ? + ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) : + 0; + } + + return compare & 4 ? -1 : 1; + } : + function( a, b ) { + // Exit early if the nodes are identical + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + var cur, + i = 0, + aup = a.parentNode, + bup = b.parentNode, + ap = [ a ], + bp = [ b ]; + + // Parentless nodes are either documents or disconnected + if ( !aup || !bup ) { + return a === doc ? -1 : + b === doc ? 1 : + aup ? -1 : + bup ? 1 : + sortInput ? + ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) : + 0; + + // If the nodes are siblings, we can do a quick check + } else if ( aup === bup ) { + return siblingCheck( a, b ); + } + + // Otherwise we need full lists of their ancestors for comparison + cur = a; + while ( (cur = cur.parentNode) ) { + ap.unshift( cur ); + } + cur = b; + while ( (cur = cur.parentNode) ) { + bp.unshift( cur ); + } + + // Walk down the tree looking for a discrepancy + while ( ap[i] === bp[i] ) { + i++; + } + + return i ? + // Do a sibling check if the nodes have a common ancestor + siblingCheck( ap[i], bp[i] ) : + + // Otherwise nodes in our document sort first + ap[i] === preferredDoc ? -1 : + bp[i] === preferredDoc ? 1 : + 0; + }; + + return doc; +}; + +Sizzle.matches = function( expr, elements ) { + return Sizzle( expr, null, null, elements ); +}; + +Sizzle.matchesSelector = function( elem, expr ) { + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + // Make sure that attribute selectors are quoted + expr = expr.replace( rattributeQuotes, "='$1']" ); + + if ( support.matchesSelector && documentIsHTML && + ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && + ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { + + try { + var ret = matches.call( elem, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || support.disconnectedMatch || + // As well, disconnected nodes are said to be in a document + // fragment in IE 9 + elem.document && elem.document.nodeType !== 11 ) { + return ret; + } + } catch(e) {} + } + + return Sizzle( expr, document, null, [elem] ).length > 0; +}; + +Sizzle.contains = function( context, elem ) { + // Set document vars if needed + if ( ( context.ownerDocument || context ) !== document ) { + setDocument( context ); + } + return contains( context, elem ); +}; + +Sizzle.attr = function( elem, name ) { + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + var fn = Expr.attrHandle[ name.toLowerCase() ], + // Don't get fooled by Object.prototype properties (jQuery #13807) + val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? + fn( elem, name, !documentIsHTML ) : + undefined; + + return val !== undefined ? + val : + support.attributes || !documentIsHTML ? + elem.getAttribute( name ) : + (val = elem.getAttributeNode(name)) && val.specified ? + val.value : + null; +}; + +Sizzle.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +/** + * Document sorting and removing duplicates + * @param {ArrayLike} results + */ +Sizzle.uniqueSort = function( results ) { + var elem, + duplicates = [], + j = 0, + i = 0; + + // Unless we *know* we can detect duplicates, assume their presence + hasDuplicate = !support.detectDuplicates; + sortInput = !support.sortStable && results.slice( 0 ); + results.sort( sortOrder ); + + if ( hasDuplicate ) { + while ( (elem = results[i++]) ) { + if ( elem === results[ i ] ) { + j = duplicates.push( i ); + } + } + while ( j-- ) { + results.splice( duplicates[ j ], 1 ); + } + } + + // Clear input after sorting to release objects + // See https://github.com/jquery/sizzle/pull/225 + sortInput = null; + + return results; +}; + +/** + * Utility function for retrieving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ +getText = Sizzle.getText = function( elem ) { + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( !nodeType ) { + // If no nodeType, this is expected to be an array + while ( (node = elem[i++]) ) { + // Do not traverse comment nodes + ret += getText( node ); + } + } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + // Use textContent for elements + // innerText usage removed for consistency of new lines (jQuery #11153) + if ( typeof elem.textContent === "string" ) { + return elem.textContent; + } else { + // Traverse its children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + // Do not include comment or processing instruction nodes + + return ret; +}; + +Expr = Sizzle.selectors = { + + // Can be adjusted by the user + cacheLength: 50, + + createPseudo: markFunction, + + match: matchExpr, + + attrHandle: {}, + + find: {}, + + relative: { + ">": { dir: "parentNode", first: true }, + " ": { dir: "parentNode" }, + "+": { dir: "previousSibling", first: true }, + "~": { dir: "previousSibling" } + }, + + preFilter: { + "ATTR": function( match ) { + match[1] = match[1].replace( runescape, funescape ); + + // Move the given value to match[3] whether quoted or unquoted + match[3] = ( match[4] || match[5] || "" ).replace( runescape, funescape ); + + if ( match[2] === "~=" ) { + match[3] = " " + match[3] + " "; + } + + return match.slice( 0, 4 ); + }, + + "CHILD": function( match ) { + /* matches from matchExpr["CHILD"] + 1 type (only|nth|...) + 2 what (child|of-type) + 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) + 4 xn-component of xn+y argument ([+-]?\d*n|) + 5 sign of xn-component + 6 x of xn-component + 7 sign of y-component + 8 y of y-component + */ + match[1] = match[1].toLowerCase(); + + if ( match[1].slice( 0, 3 ) === "nth" ) { + // nth-* requires argument + if ( !match[3] ) { + Sizzle.error( match[0] ); + } + + // numeric x and y parameters for Expr.filter.CHILD + // remember that false/true cast respectively to 0/1 + match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) ); + match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" ); + + // other types prohibit arguments + } else if ( match[3] ) { + Sizzle.error( match[0] ); + } + + return match; + }, + + "PSEUDO": function( match ) { + var excess, + unquoted = !match[5] && match[2]; + + if ( matchExpr["CHILD"].test( match[0] ) ) { + return null; + } + + // Accept quoted arguments as-is + if ( match[3] && match[4] !== undefined ) { + match[2] = match[4]; + + // Strip excess characters from unquoted arguments + } else if ( unquoted && rpseudo.test( unquoted ) && + // Get excess from tokenize (recursively) + (excess = tokenize( unquoted, true )) && + // advance to the next closing parenthesis + (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) { + + // excess is a negative index + match[0] = match[0].slice( 0, excess ); + match[2] = unquoted.slice( 0, excess ); + } + + // Return only captures needed by the pseudo filter method (type and argument) + return match.slice( 0, 3 ); + } + }, + + filter: { + + "TAG": function( nodeNameSelector ) { + var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); + return nodeNameSelector === "*" ? + function() { return true; } : + function( elem ) { + return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; + }; + }, + + "CLASS": function( className ) { + var pattern = classCache[ className + " " ]; + + return pattern || + (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) && + classCache( className, function( elem ) { + return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== strundefined && elem.getAttribute("class") || "" ); + }); + }, + + "ATTR": function( name, operator, check ) { + return function( elem ) { + var result = Sizzle.attr( elem, name ); + + if ( result == null ) { + return operator === "!="; + } + if ( !operator ) { + return true; + } + + result += ""; + + return operator === "=" ? result === check : + operator === "!=" ? result !== check : + operator === "^=" ? check && result.indexOf( check ) === 0 : + operator === "*=" ? check && result.indexOf( check ) > -1 : + operator === "$=" ? check && result.slice( -check.length ) === check : + operator === "~=" ? ( " " + result + " " ).indexOf( check ) > -1 : + operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : + false; + }; + }, + + "CHILD": function( type, what, argument, first, last ) { + var simple = type.slice( 0, 3 ) !== "nth", + forward = type.slice( -4 ) !== "last", + ofType = what === "of-type"; + + return first === 1 && last === 0 ? + + // Shortcut for :nth-*(n) + function( elem ) { + return !!elem.parentNode; + } : + + function( elem, context, xml ) { + var cache, outerCache, node, diff, nodeIndex, start, + dir = simple !== forward ? "nextSibling" : "previousSibling", + parent = elem.parentNode, + name = ofType && elem.nodeName.toLowerCase(), + useCache = !xml && !ofType; + + if ( parent ) { + + // :(first|last|only)-(child|of-type) + if ( simple ) { + while ( dir ) { + node = elem; + while ( (node = node[ dir ]) ) { + if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) { + return false; + } + } + // Reverse direction for :only-* (if we haven't yet done so) + start = dir = type === "only" && !start && "nextSibling"; + } + return true; + } + + start = [ forward ? parent.firstChild : parent.lastChild ]; + + // non-xml :nth-child(...) stores cache data on `parent` + if ( forward && useCache ) { + // Seek `elem` from a previously-cached index + outerCache = parent[ expando ] || (parent[ expando ] = {}); + cache = outerCache[ type ] || []; + nodeIndex = cache[0] === dirruns && cache[1]; + diff = cache[0] === dirruns && cache[2]; + node = nodeIndex && parent.childNodes[ nodeIndex ]; + + while ( (node = ++nodeIndex && node && node[ dir ] || + + // Fallback to seeking `elem` from the start + (diff = nodeIndex = 0) || start.pop()) ) { + + // When found, cache indexes on `parent` and break + if ( node.nodeType === 1 && ++diff && node === elem ) { + outerCache[ type ] = [ dirruns, nodeIndex, diff ]; + break; + } + } + + // Use previously-cached element index if available + } else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns ) { + diff = cache[1]; + + // xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...) + } else { + // Use the same loop as above to seek `elem` from the start + while ( (node = ++nodeIndex && node && node[ dir ] || + (diff = nodeIndex = 0) || start.pop()) ) { + + if ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) { + // Cache the index of each encountered element + if ( useCache ) { + (node[ expando ] || (node[ expando ] = {}))[ type ] = [ dirruns, diff ]; + } + + if ( node === elem ) { + break; + } + } + } + } + + // Incorporate the offset, then check against cycle size + diff -= last; + return diff === first || ( diff % first === 0 && diff / first >= 0 ); + } + }; + }, + + "PSEUDO": function( pseudo, argument ) { + // pseudo-class names are case-insensitive + // http://www.w3.org/TR/selectors/#pseudo-classes + // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters + // Remember that setFilters inherits from pseudos + var args, + fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || + Sizzle.error( "unsupported pseudo: " + pseudo ); + + // The user may use createPseudo to indicate that + // arguments are needed to create the filter function + // just as Sizzle does + if ( fn[ expando ] ) { + return fn( argument ); + } + + // But maintain support for old signatures + if ( fn.length > 1 ) { + args = [ pseudo, pseudo, "", argument ]; + return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? + markFunction(function( seed, matches ) { + var idx, + matched = fn( seed, argument ), + i = matched.length; + while ( i-- ) { + idx = indexOf.call( seed, matched[i] ); + seed[ idx ] = !( matches[ idx ] = matched[i] ); + } + }) : + function( elem ) { + return fn( elem, 0, args ); + }; + } + + return fn; + } + }, + + pseudos: { + // Potentially complex pseudos + "not": markFunction(function( selector ) { + // Trim the selector passed to compile + // to avoid treating leading and trailing + // spaces as combinators + var input = [], + results = [], + matcher = compile( selector.replace( rtrim, "$1" ) ); + + return matcher[ expando ] ? + markFunction(function( seed, matches, context, xml ) { + var elem, + unmatched = matcher( seed, null, xml, [] ), + i = seed.length; + + // Match elements unmatched by `matcher` + while ( i-- ) { + if ( (elem = unmatched[i]) ) { + seed[i] = !(matches[i] = elem); + } + } + }) : + function( elem, context, xml ) { + input[0] = elem; + matcher( input, null, xml, results ); + return !results.pop(); + }; + }), + + "has": markFunction(function( selector ) { + return function( elem ) { + return Sizzle( selector, elem ).length > 0; + }; + }), + + "contains": markFunction(function( text ) { + return function( elem ) { + return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1; + }; + }), + + // "Whether an element is represented by a :lang() selector + // is based solely on the element's language value + // being equal to the identifier C, + // or beginning with the identifier C immediately followed by "-". + // The matching of C against the element's language value is performed case-insensitively. + // The identifier C does not have to be a valid language name." + // http://www.w3.org/TR/selectors/#lang-pseudo + "lang": markFunction( function( lang ) { + // lang value must be a valid identifier + if ( !ridentifier.test(lang || "") ) { + Sizzle.error( "unsupported lang: " + lang ); + } + lang = lang.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + var elemLang; + do { + if ( (elemLang = documentIsHTML ? + elem.lang : + elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) { + + elemLang = elemLang.toLowerCase(); + return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; + } + } while ( (elem = elem.parentNode) && elem.nodeType === 1 ); + return false; + }; + }), + + // Miscellaneous + "target": function( elem ) { + var hash = window.location && window.location.hash; + return hash && hash.slice( 1 ) === elem.id; + }, + + "root": function( elem ) { + return elem === docElem; + }, + + "focus": function( elem ) { + return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); + }, + + // Boolean properties + "enabled": function( elem ) { + return elem.disabled === false; + }, + + "disabled": function( elem ) { + return elem.disabled === true; + }, + + "checked": function( elem ) { + // In CSS3, :checked should return both checked and selected elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + var nodeName = elem.nodeName.toLowerCase(); + return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected); + }, + + "selected": function( elem ) { + // Accessing this property makes selected-by-default + // options in Safari work properly + if ( elem.parentNode ) { + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + // Contents + "empty": function( elem ) { + // http://www.w3.org/TR/selectors/#empty-pseudo + // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), + // but not by others (comment: 8; processing instruction: 7; etc.) + // nodeType < 6 works because attributes (2) do not appear as children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + if ( elem.nodeType < 6 ) { + return false; + } + } + return true; + }, + + "parent": function( elem ) { + return !Expr.pseudos["empty"]( elem ); + }, + + // Element/input types + "header": function( elem ) { + return rheader.test( elem.nodeName ); + }, + + "input": function( elem ) { + return rinputs.test( elem.nodeName ); + }, + + "button": function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === "button" || name === "button"; + }, + + "text": function( elem ) { + var attr; + return elem.nodeName.toLowerCase() === "input" && + elem.type === "text" && + + // Support: IE<8 + // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" + ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" ); + }, + + // Position-in-collection + "first": createPositionalPseudo(function() { + return [ 0 ]; + }), + + "last": createPositionalPseudo(function( matchIndexes, length ) { + return [ length - 1 ]; + }), + + "eq": createPositionalPseudo(function( matchIndexes, length, argument ) { + return [ argument < 0 ? argument + length : argument ]; + }), + + "even": createPositionalPseudo(function( matchIndexes, length ) { + var i = 0; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "odd": createPositionalPseudo(function( matchIndexes, length ) { + var i = 1; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "lt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; --i >= 0; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "gt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; ++i < length; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }) + } +}; + +Expr.pseudos["nth"] = Expr.pseudos["eq"]; + +// Add button/input type pseudos +for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { + Expr.pseudos[ i ] = createInputPseudo( i ); +} +for ( i in { submit: true, reset: true } ) { + Expr.pseudos[ i ] = createButtonPseudo( i ); +} + +// Easy API for creating new setFilters +function setFilters() {} +setFilters.prototype = Expr.filters = Expr.pseudos; +Expr.setFilters = new setFilters(); + +function tokenize( selector, parseOnly ) { + var matched, match, tokens, type, + soFar, groups, preFilters, + cached = tokenCache[ selector + " " ]; + + if ( cached ) { + return parseOnly ? 0 : cached.slice( 0 ); + } + + soFar = selector; + groups = []; + preFilters = Expr.preFilter; + + while ( soFar ) { + + // Comma and first run + if ( !matched || (match = rcomma.exec( soFar )) ) { + if ( match ) { + // Don't consume trailing commas as valid + soFar = soFar.slice( match[0].length ) || soFar; + } + groups.push( (tokens = []) ); + } + + matched = false; + + // Combinators + if ( (match = rcombinators.exec( soFar )) ) { + matched = match.shift(); + tokens.push({ + value: matched, + // Cast descendant combinators to space + type: match[0].replace( rtrim, " " ) + }); + soFar = soFar.slice( matched.length ); + } + + // Filters + for ( type in Expr.filter ) { + if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] || + (match = preFilters[ type ]( match ))) ) { + matched = match.shift(); + tokens.push({ + value: matched, + type: type, + matches: match + }); + soFar = soFar.slice( matched.length ); + } + } + + if ( !matched ) { + break; + } + } + + // Return the length of the invalid excess + // if we're just parsing + // Otherwise, throw an error or return tokens + return parseOnly ? + soFar.length : + soFar ? + Sizzle.error( selector ) : + // Cache the tokens + tokenCache( selector, groups ).slice( 0 ); +} + +function toSelector( tokens ) { + var i = 0, + len = tokens.length, + selector = ""; + for ( ; i < len; i++ ) { + selector += tokens[i].value; + } + return selector; +} + +function addCombinator( matcher, combinator, base ) { + var dir = combinator.dir, + checkNonElements = base && dir === "parentNode", + doneName = done++; + + return combinator.first ? + // Check against closest ancestor/preceding element + function( elem, context, xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + return matcher( elem, context, xml ); + } + } + } : + + // Check against all ancestor/preceding elements + function( elem, context, xml ) { + var oldCache, outerCache, + newCache = [ dirruns, doneName ]; + + // We can't set arbitrary data on XML nodes, so they don't benefit from dir caching + if ( xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + if ( matcher( elem, context, xml ) ) { + return true; + } + } + } + } else { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + outerCache = elem[ expando ] || (elem[ expando ] = {}); + if ( (oldCache = outerCache[ dir ]) && + oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { + + // Assign to newCache so results back-propagate to previous elements + return (newCache[ 2 ] = oldCache[ 2 ]); + } else { + // Reuse newcache so results back-propagate to previous elements + outerCache[ dir ] = newCache; + + // A match means we're done; a fail means we have to keep checking + if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) { + return true; + } + } + } + } + } + }; +} + +function elementMatcher( matchers ) { + return matchers.length > 1 ? + function( elem, context, xml ) { + var i = matchers.length; + while ( i-- ) { + if ( !matchers[i]( elem, context, xml ) ) { + return false; + } + } + return true; + } : + matchers[0]; +} + +function condense( unmatched, map, filter, context, xml ) { + var elem, + newUnmatched = [], + i = 0, + len = unmatched.length, + mapped = map != null; + + for ( ; i < len; i++ ) { + if ( (elem = unmatched[i]) ) { + if ( !filter || filter( elem, context, xml ) ) { + newUnmatched.push( elem ); + if ( mapped ) { + map.push( i ); + } + } + } + } + + return newUnmatched; +} + +function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { + if ( postFilter && !postFilter[ expando ] ) { + postFilter = setMatcher( postFilter ); + } + if ( postFinder && !postFinder[ expando ] ) { + postFinder = setMatcher( postFinder, postSelector ); + } + return markFunction(function( seed, results, context, xml ) { + var temp, i, elem, + preMap = [], + postMap = [], + preexisting = results.length, + + // Get initial elements from seed or context + elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), + + // Prefilter to get matcher input, preserving a map for seed-results synchronization + matcherIn = preFilter && ( seed || !selector ) ? + condense( elems, preMap, preFilter, context, xml ) : + elems, + + matcherOut = matcher ? + // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, + postFinder || ( seed ? preFilter : preexisting || postFilter ) ? + + // ...intermediate processing is necessary + [] : + + // ...otherwise use results directly + results : + matcherIn; + + // Find primary matches + if ( matcher ) { + matcher( matcherIn, matcherOut, context, xml ); + } + + // Apply postFilter + if ( postFilter ) { + temp = condense( matcherOut, postMap ); + postFilter( temp, [], context, xml ); + + // Un-match failing elements by moving them back to matcherIn + i = temp.length; + while ( i-- ) { + if ( (elem = temp[i]) ) { + matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem); + } + } + } + + if ( seed ) { + if ( postFinder || preFilter ) { + if ( postFinder ) { + // Get the final matcherOut by condensing this intermediate into postFinder contexts + temp = []; + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) ) { + // Restore matcherIn since elem is not yet a final match + temp.push( (matcherIn[i] = elem) ); + } + } + postFinder( null, (matcherOut = []), temp, xml ); + } + + // Move matched elements from seed to results to keep them synchronized + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) && + (temp = postFinder ? indexOf.call( seed, elem ) : preMap[i]) > -1 ) { + + seed[temp] = !(results[temp] = elem); + } + } + } + + // Add elements to results, through postFinder if defined + } else { + matcherOut = condense( + matcherOut === results ? + matcherOut.splice( preexisting, matcherOut.length ) : + matcherOut + ); + if ( postFinder ) { + postFinder( null, results, matcherOut, xml ); + } else { + push.apply( results, matcherOut ); + } + } + }); +} + +function matcherFromTokens( tokens ) { + var checkContext, matcher, j, + len = tokens.length, + leadingRelative = Expr.relative[ tokens[0].type ], + implicitRelative = leadingRelative || Expr.relative[" "], + i = leadingRelative ? 1 : 0, + + // The foundational matcher ensures that elements are reachable from top-level context(s) + matchContext = addCombinator( function( elem ) { + return elem === checkContext; + }, implicitRelative, true ), + matchAnyContext = addCombinator( function( elem ) { + return indexOf.call( checkContext, elem ) > -1; + }, implicitRelative, true ), + matchers = [ function( elem, context, xml ) { + return ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( + (checkContext = context).nodeType ? + matchContext( elem, context, xml ) : + matchAnyContext( elem, context, xml ) ); + } ]; + + for ( ; i < len; i++ ) { + if ( (matcher = Expr.relative[ tokens[i].type ]) ) { + matchers = [ addCombinator(elementMatcher( matchers ), matcher) ]; + } else { + matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches ); + + // Return special upon seeing a positional matcher + if ( matcher[ expando ] ) { + // Find the next relative operator (if any) for proper handling + j = ++i; + for ( ; j < len; j++ ) { + if ( Expr.relative[ tokens[j].type ] ) { + break; + } + } + return setMatcher( + i > 1 && elementMatcher( matchers ), + i > 1 && toSelector( + // If the preceding token was a descendant combinator, insert an implicit any-element `*` + tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" }) + ).replace( rtrim, "$1" ), + matcher, + i < j && matcherFromTokens( tokens.slice( i, j ) ), + j < len && matcherFromTokens( (tokens = tokens.slice( j )) ), + j < len && toSelector( tokens ) + ); + } + matchers.push( matcher ); + } + } + + return elementMatcher( matchers ); +} + +function matcherFromGroupMatchers( elementMatchers, setMatchers ) { + var bySet = setMatchers.length > 0, + byElement = elementMatchers.length > 0, + superMatcher = function( seed, context, xml, results, outermost ) { + var elem, j, matcher, + matchedCount = 0, + i = "0", + unmatched = seed && [], + setMatched = [], + contextBackup = outermostContext, + // We must always have either seed elements or outermost context + elems = seed || byElement && Expr.find["TAG"]( "*", outermost ), + // Use integer dirruns iff this is the outermost matcher + dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1), + len = elems.length; + + if ( outermost ) { + outermostContext = context !== document && context; + } + + // Add elements passing elementMatchers directly to results + // Keep `i` a string if there are no elements so `matchedCount` will be "00" below + // Support: IE<9, Safari + // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id + for ( ; i !== len && (elem = elems[i]) != null; i++ ) { + if ( byElement && elem ) { + j = 0; + while ( (matcher = elementMatchers[j++]) ) { + if ( matcher( elem, context, xml ) ) { + results.push( elem ); + break; + } + } + if ( outermost ) { + dirruns = dirrunsUnique; + } + } + + // Track unmatched elements for set filters + if ( bySet ) { + // They will have gone through all possible matchers + if ( (elem = !matcher && elem) ) { + matchedCount--; + } + + // Lengthen the array for every element, matched or not + if ( seed ) { + unmatched.push( elem ); + } + } + } + + // Apply set filters to unmatched elements + matchedCount += i; + if ( bySet && i !== matchedCount ) { + j = 0; + while ( (matcher = setMatchers[j++]) ) { + matcher( unmatched, setMatched, context, xml ); + } + + if ( seed ) { + // Reintegrate element matches to eliminate the need for sorting + if ( matchedCount > 0 ) { + while ( i-- ) { + if ( !(unmatched[i] || setMatched[i]) ) { + setMatched[i] = pop.call( results ); + } + } + } + + // Discard index placeholder values to get only actual matches + setMatched = condense( setMatched ); + } + + // Add matches to results + push.apply( results, setMatched ); + + // Seedless set matches succeeding multiple successful matchers stipulate sorting + if ( outermost && !seed && setMatched.length > 0 && + ( matchedCount + setMatchers.length ) > 1 ) { + + Sizzle.uniqueSort( results ); + } + } + + // Override manipulation of globals by nested matchers + if ( outermost ) { + dirruns = dirrunsUnique; + outermostContext = contextBackup; + } + + return unmatched; + }; + + return bySet ? + markFunction( superMatcher ) : + superMatcher; +} + +compile = Sizzle.compile = function( selector, group /* Internal Use Only */ ) { + var i, + setMatchers = [], + elementMatchers = [], + cached = compilerCache[ selector + " " ]; + + if ( !cached ) { + // Generate a function of recursive functions that can be used to check each element + if ( !group ) { + group = tokenize( selector ); + } + i = group.length; + while ( i-- ) { + cached = matcherFromTokens( group[i] ); + if ( cached[ expando ] ) { + setMatchers.push( cached ); + } else { + elementMatchers.push( cached ); + } + } + + // Cache the compiled function + cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) ); + } + return cached; +}; + +function multipleContexts( selector, contexts, results ) { + var i = 0, + len = contexts.length; + for ( ; i < len; i++ ) { + Sizzle( selector, contexts[i], results ); + } + return results; +} + +function select( selector, context, results, seed ) { + var i, tokens, token, type, find, + match = tokenize( selector ); + + if ( !seed ) { + // Try to minimize operations if there is only one group + if ( match.length === 1 ) { + + // Take a shortcut and set the context if the root selector is an ID + tokens = match[0] = match[0].slice( 0 ); + if ( tokens.length > 2 && (token = tokens[0]).type === "ID" && + support.getById && context.nodeType === 9 && documentIsHTML && + Expr.relative[ tokens[1].type ] ) { + + context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0]; + if ( !context ) { + return results; + } + selector = selector.slice( tokens.shift().value.length ); + } + + // Fetch a seed set for right-to-left matching + i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length; + while ( i-- ) { + token = tokens[i]; + + // Abort if we hit a combinator + if ( Expr.relative[ (type = token.type) ] ) { + break; + } + if ( (find = Expr.find[ type ]) ) { + // Search, expanding context for leading sibling combinators + if ( (seed = find( + token.matches[0].replace( runescape, funescape ), + rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context + )) ) { + + // If seed is empty or no tokens remain, we can return early + tokens.splice( i, 1 ); + selector = seed.length && toSelector( tokens ); + if ( !selector ) { + push.apply( results, seed ); + return results; + } + + break; + } + } + } + } + } + + // Compile and execute a filtering function + // Provide `match` to avoid retokenization if we modified the selector above + compile( selector, match )( + seed, + context, + !documentIsHTML, + results, + rsibling.test( selector ) && testContext( context.parentNode ) || context + ); + return results; +} + +// One-time assignments + +// Sort stability +support.sortStable = expando.split("").sort( sortOrder ).join("") === expando; + +// Support: Chrome<14 +// Always assume duplicates if they aren't passed to the comparison function +support.detectDuplicates = !!hasDuplicate; + +// Initialize against the default document +setDocument(); + +// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) +// Detached nodes confoundingly follow *each other* +support.sortDetached = assert(function( div1 ) { + // Should return 1, but returns 4 (following) + return div1.compareDocumentPosition( document.createElement("div") ) & 1; +}); + +// Support: IE<8 +// Prevent attribute/property "interpolation" +// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx +if ( !assert(function( div ) { + div.innerHTML = ""; + return div.firstChild.getAttribute("href") === "#" ; +}) ) { + addHandle( "type|href|height|width", function( elem, name, isXML ) { + if ( !isXML ) { + return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); + } + }); +} + +// Support: IE<9 +// Use defaultValue in place of getAttribute("value") +if ( !support.attributes || !assert(function( div ) { + div.innerHTML = ""; + div.firstChild.setAttribute( "value", "" ); + return div.firstChild.getAttribute( "value" ) === ""; +}) ) { + addHandle( "value", function( elem, name, isXML ) { + if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { + return elem.defaultValue; + } + }); +} + +// Support: IE<9 +// Use getAttributeNode to fetch booleans when getAttribute lies +if ( !assert(function( div ) { + return div.getAttribute("disabled") == null; +}) ) { + addHandle( booleans, function( elem, name, isXML ) { + var val; + if ( !isXML ) { + return elem[ name ] === true ? name.toLowerCase() : + (val = elem.getAttributeNode( name )) && val.specified ? + val.value : + null; + } + }); +} + +return Sizzle; + +})( window ); + + + +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; +jQuery.expr[":"] = jQuery.expr.pseudos; +jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; + + + +var rneedsContext = jQuery.expr.match.needsContext; + +var rsingleTag = (/^<(\w+)\s*\/?>(?:<\/\1>|)$/); + + + +var risSimple = /^.[^:#\[\.,]*$/; + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, not ) { + if ( jQuery.isFunction( qualifier ) ) { + return jQuery.grep( elements, function( elem, i ) { + /* jshint -W018 */ + return !!qualifier.call( elem, i, elem ) !== not; + }); + + } + + if ( qualifier.nodeType ) { + return jQuery.grep( elements, function( elem ) { + return ( elem === qualifier ) !== not; + }); + + } + + if ( typeof qualifier === "string" ) { + if ( risSimple.test( qualifier ) ) { + return jQuery.filter( qualifier, elements, not ); + } + + qualifier = jQuery.filter( qualifier, elements ); + } + + return jQuery.grep( elements, function( elem ) { + return ( indexOf.call( qualifier, elem ) >= 0 ) !== not; + }); +} + +jQuery.filter = function( expr, elems, not ) { + var elem = elems[ 0 ]; + + if ( not ) { + expr = ":not(" + expr + ")"; + } + + return elems.length === 1 && elem.nodeType === 1 ? + jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [] : + jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { + return elem.nodeType === 1; + })); +}; + +jQuery.fn.extend({ + find: function( selector ) { + var i, + len = this.length, + ret = [], + self = this; + + if ( typeof selector !== "string" ) { + return this.pushStack( jQuery( selector ).filter(function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + }) ); + } + + for ( i = 0; i < len; i++ ) { + jQuery.find( selector, self[ i ], ret ); + } + + // Needed because $( selector, context ) becomes $( context ).find( selector ) + ret = this.pushStack( len > 1 ? jQuery.unique( ret ) : ret ); + ret.selector = this.selector ? this.selector + " " + selector : selector; + return ret; + }, + filter: function( selector ) { + return this.pushStack( winnow(this, selector || [], false) ); + }, + not: function( selector ) { + return this.pushStack( winnow(this, selector || [], true) ); + }, + is: function( selector ) { + return !!winnow( + this, + + // If this is a positional/relative selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + typeof selector === "string" && rneedsContext.test( selector ) ? + jQuery( selector ) : + selector || [], + false + ).length; + } +}); + + +// Initialize a jQuery object + + +// A central reference to the root jQuery(document) +var rootjQuery, + + // A simple way to check for HTML strings + // Prioritize #id over to avoid XSS via location.hash (#9521) + // Strict HTML recognition (#11290: must start with <) + rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/, + + init = jQuery.fn.init = function( selector, context ) { + var match, elem; + + // HANDLE: $(""), $(null), $(undefined), $(false) + if ( !selector ) { + return this; + } + + // Handle HTML strings + if ( typeof selector === "string" ) { + if ( selector[0] === "<" && selector[ selector.length - 1 ] === ">" && selector.length >= 3 ) { + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = rquickExpr.exec( selector ); + } + + // Match html or make sure no context is specified for #id + if ( match && (match[1] || !context) ) { + + // HANDLE: $(html) -> $(array) + if ( match[1] ) { + context = context instanceof jQuery ? context[0] : context; + + // scripts is true for back-compat + // Intentionally let the error be thrown if parseHTML is not present + jQuery.merge( this, jQuery.parseHTML( + match[1], + context && context.nodeType ? context.ownerDocument || context : document, + true + ) ); + + // HANDLE: $(html, props) + if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) { + for ( match in context ) { + // Properties of context are called as methods if possible + if ( jQuery.isFunction( this[ match ] ) ) { + this[ match ]( context[ match ] ); + + // ...and otherwise set as attributes + } else { + this.attr( match, context[ match ] ); + } + } + } + + return this; + + // HANDLE: $(#id) + } else { + elem = document.getElementById( match[2] ); + + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if ( elem && elem.parentNode ) { + // Inject the element directly into the jQuery object + this.length = 1; + this[0] = elem; + } + + this.context = document; + this.selector = selector; + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return ( context || rootjQuery ).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } + + // HANDLE: $(DOMElement) + } else if ( selector.nodeType ) { + this.context = this[0] = selector; + this.length = 1; + return this; + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( jQuery.isFunction( selector ) ) { + return typeof rootjQuery.ready !== "undefined" ? + rootjQuery.ready( selector ) : + // Execute immediately if ready is not present + selector( jQuery ); + } + + if ( selector.selector !== undefined ) { + this.selector = selector.selector; + this.context = selector.context; + } + + return jQuery.makeArray( selector, this ); + }; + +// Give the init function the jQuery prototype for later instantiation +init.prototype = jQuery.fn; + +// Initialize central reference +rootjQuery = jQuery( document ); + + +var rparentsprev = /^(?:parents|prev(?:Until|All))/, + // methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.extend({ + dir: function( elem, dir, until ) { + var matched = [], + truncate = until !== undefined; + + while ( (elem = elem[ dir ]) && elem.nodeType !== 9 ) { + if ( elem.nodeType === 1 ) { + if ( truncate && jQuery( elem ).is( until ) ) { + break; + } + matched.push( elem ); + } + } + return matched; + }, + + sibling: function( n, elem ) { + var matched = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + matched.push( n ); + } + } + + return matched; + } +}); + +jQuery.fn.extend({ + has: function( target ) { + var targets = jQuery( target, this ), + l = targets.length; + + return this.filter(function() { + var i = 0; + for ( ; i < l; i++ ) { + if ( jQuery.contains( this, targets[i] ) ) { + return true; + } + } + }); + }, + + closest: function( selectors, context ) { + var cur, + i = 0, + l = this.length, + matched = [], + pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ? + jQuery( selectors, context || this.context ) : + 0; + + for ( ; i < l; i++ ) { + for ( cur = this[i]; cur && cur !== context; cur = cur.parentNode ) { + // Always skip document fragments + if ( cur.nodeType < 11 && (pos ? + pos.index(cur) > -1 : + + // Don't pass non-elements to Sizzle + cur.nodeType === 1 && + jQuery.find.matchesSelector(cur, selectors)) ) { + + matched.push( cur ); + break; + } + } + } + + return this.pushStack( matched.length > 1 ? jQuery.unique( matched ) : matched ); + }, + + // Determine the position of an element within + // the matched set of elements + index: function( elem ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; + } + + // index in selector + if ( typeof elem === "string" ) { + return indexOf.call( jQuery( elem ), this[ 0 ] ); + } + + // Locate the position of the desired element + return indexOf.call( this, + + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[ 0 ] : elem + ); + }, + + add: function( selector, context ) { + return this.pushStack( + jQuery.unique( + jQuery.merge( this.get(), jQuery( selector, context ) ) + ) + ); + }, + + addBack: function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter(selector) + ); + } +}); + +function sibling( cur, dir ) { + while ( (cur = cur[dir]) && cur.nodeType !== 1 ) {} + return cur; +} + +jQuery.each({ + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return jQuery.dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, i, until ) { + return jQuery.dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return sibling( elem, "nextSibling" ); + }, + prev: function( elem ) { + return sibling( elem, "previousSibling" ); + }, + nextAll: function( elem ) { + return jQuery.dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return jQuery.dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, i, until ) { + return jQuery.dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, i, until ) { + return jQuery.dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem ); + }, + children: function( elem ) { + return jQuery.sibling( elem.firstChild ); + }, + contents: function( elem ) { + return elem.contentDocument || jQuery.merge( [], elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var matched = jQuery.map( this, fn, until ); + + if ( name.slice( -5 ) !== "Until" ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + matched = jQuery.filter( selector, matched ); + } + + if ( this.length > 1 ) { + // Remove duplicates + if ( !guaranteedUnique[ name ] ) { + jQuery.unique( matched ); + } + + // Reverse order for parents* and prev-derivatives + if ( rparentsprev.test( name ) ) { + matched.reverse(); + } + } + + return this.pushStack( matched ); + }; +}); +var rnotwhite = (/\S+/g); + + + +// String to Object options format cache +var optionsCache = {}; + +// Convert String-formatted options into Object-formatted ones and store in cache +function createOptions( options ) { + var object = optionsCache[ options ] = {}; + jQuery.each( options.match( rnotwhite ) || [], function( _, flag ) { + object[ flag ] = true; + }); + return object; +} + +/* + * Create a callback list using the following parameters: + * + * options: an optional list of space-separated options that will change how + * the callback list behaves or a more traditional option object + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible options: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ +jQuery.Callbacks = function( options ) { + + // Convert options from String-formatted to Object-formatted if needed + // (we check in cache first) + options = typeof options === "string" ? + ( optionsCache[ options ] || createOptions( options ) ) : + jQuery.extend( {}, options ); + + var // Last fire value (for non-forgettable lists) + memory, + // Flag to know if list was already fired + fired, + // Flag to know if list is currently firing + firing, + // First callback to fire (used internally by add and fireWith) + firingStart, + // End of the loop when firing + firingLength, + // Index of currently firing callback (modified by remove if needed) + firingIndex, + // Actual callback list + list = [], + // Stack of fire calls for repeatable lists + stack = !options.once && [], + // Fire callbacks + fire = function( data ) { + memory = options.memory && data; + fired = true; + firingIndex = firingStart || 0; + firingStart = 0; + firingLength = list.length; + firing = true; + for ( ; list && firingIndex < firingLength; firingIndex++ ) { + if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) { + memory = false; // To prevent further calls using add + break; + } + } + firing = false; + if ( list ) { + if ( stack ) { + if ( stack.length ) { + fire( stack.shift() ); + } + } else if ( memory ) { + list = []; + } else { + self.disable(); + } + } + }, + // Actual Callbacks object + self = { + // Add a callback or a collection of callbacks to the list + add: function() { + if ( list ) { + // First, we save the current length + var start = list.length; + (function add( args ) { + jQuery.each( args, function( _, arg ) { + var type = jQuery.type( arg ); + if ( type === "function" ) { + if ( !options.unique || !self.has( arg ) ) { + list.push( arg ); + } + } else if ( arg && arg.length && type !== "string" ) { + // Inspect recursively + add( arg ); + } + }); + })( arguments ); + // Do we need to add the callbacks to the + // current firing batch? + if ( firing ) { + firingLength = list.length; + // With memory, if we're not firing then + // we should call right away + } else if ( memory ) { + firingStart = start; + fire( memory ); + } + } + return this; + }, + // Remove a callback from the list + remove: function() { + if ( list ) { + jQuery.each( arguments, function( _, arg ) { + var index; + while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { + list.splice( index, 1 ); + // Handle firing indexes + if ( firing ) { + if ( index <= firingLength ) { + firingLength--; + } + if ( index <= firingIndex ) { + firingIndex--; + } + } + } + }); + } + return this; + }, + // Check if a given callback is in the list. + // If no argument is given, return whether or not list has callbacks attached. + has: function( fn ) { + return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length ); + }, + // Remove all callbacks from the list + empty: function() { + list = []; + firingLength = 0; + return this; + }, + // Have the list do nothing anymore + disable: function() { + list = stack = memory = undefined; + return this; + }, + // Is it disabled? + disabled: function() { + return !list; + }, + // Lock the list in its current state + lock: function() { + stack = undefined; + if ( !memory ) { + self.disable(); + } + return this; + }, + // Is it locked? + locked: function() { + return !stack; + }, + // Call all callbacks with the given context and arguments + fireWith: function( context, args ) { + if ( list && ( !fired || stack ) ) { + args = args || []; + args = [ context, args.slice ? args.slice() : args ]; + if ( firing ) { + stack.push( args ); + } else { + fire( args ); + } + } + return this; + }, + // Call all the callbacks with the given arguments + fire: function() { + self.fireWith( this, arguments ); + return this; + }, + // To know if the callbacks have already been called at least once + fired: function() { + return !!fired; + } + }; + + return self; +}; + + +jQuery.extend({ + + Deferred: function( func ) { + var tuples = [ + // action, add listener, listener list, final state + [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ], + [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ], + [ "notify", "progress", jQuery.Callbacks("memory") ] + ], + state = "pending", + promise = { + state: function() { + return state; + }, + always: function() { + deferred.done( arguments ).fail( arguments ); + return this; + }, + then: function( /* fnDone, fnFail, fnProgress */ ) { + var fns = arguments; + return jQuery.Deferred(function( newDefer ) { + jQuery.each( tuples, function( i, tuple ) { + var fn = jQuery.isFunction( fns[ i ] ) && fns[ i ]; + // deferred[ done | fail | progress ] for forwarding actions to newDefer + deferred[ tuple[1] ](function() { + var returned = fn && fn.apply( this, arguments ); + if ( returned && jQuery.isFunction( returned.promise ) ) { + returned.promise() + .done( newDefer.resolve ) + .fail( newDefer.reject ) + .progress( newDefer.notify ); + } else { + newDefer[ tuple[ 0 ] + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments ); + } + }); + }); + fns = null; + }).promise(); + }, + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + return obj != null ? jQuery.extend( obj, promise ) : promise; + } + }, + deferred = {}; + + // Keep pipe for back-compat + promise.pipe = promise.then; + + // Add list-specific methods + jQuery.each( tuples, function( i, tuple ) { + var list = tuple[ 2 ], + stateString = tuple[ 3 ]; + + // promise[ done | fail | progress ] = list.add + promise[ tuple[1] ] = list.add; + + // Handle state + if ( stateString ) { + list.add(function() { + // state = [ resolved | rejected ] + state = stateString; + + // [ reject_list | resolve_list ].disable; progress_list.lock + }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock ); + } + + // deferred[ resolve | reject | notify ] + deferred[ tuple[0] ] = function() { + deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments ); + return this; + }; + deferred[ tuple[0] + "With" ] = list.fireWith; + }); + + // Make the deferred a promise + promise.promise( deferred ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function( subordinate /* , ..., subordinateN */ ) { + var i = 0, + resolveValues = slice.call( arguments ), + length = resolveValues.length, + + // the count of uncompleted subordinates + remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0, + + // the master Deferred. If resolveValues consist of only a single Deferred, just use that. + deferred = remaining === 1 ? subordinate : jQuery.Deferred(), + + // Update function for both resolve and progress values + updateFunc = function( i, contexts, values ) { + return function( value ) { + contexts[ i ] = this; + values[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; + if ( values === progressValues ) { + deferred.notifyWith( contexts, values ); + } else if ( !( --remaining ) ) { + deferred.resolveWith( contexts, values ); + } + }; + }, + + progressValues, progressContexts, resolveContexts; + + // add listeners to Deferred subordinates; treat others as resolved + if ( length > 1 ) { + progressValues = new Array( length ); + progressContexts = new Array( length ); + resolveContexts = new Array( length ); + for ( ; i < length; i++ ) { + if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) { + resolveValues[ i ].promise() + .done( updateFunc( i, resolveContexts, resolveValues ) ) + .fail( deferred.reject ) + .progress( updateFunc( i, progressContexts, progressValues ) ); + } else { + --remaining; + } + } + } + + // if we're not waiting on anything, resolve the master + if ( !remaining ) { + deferred.resolveWith( resolveContexts, resolveValues ); + } + + return deferred.promise(); + } +}); + + +// The deferred used on DOM ready +var readyList; + +jQuery.fn.ready = function( fn ) { + // Add the callback + jQuery.ready.promise().done( fn ); + + return this; +}; + +jQuery.extend({ + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Hold (or release) the ready event + holdReady: function( hold ) { + if ( hold ) { + jQuery.readyWait++; + } else { + jQuery.ready( true ); + } + }, + + // Handle when the DOM is ready + ready: function( wait ) { + + // Abort if there are pending holds or we're already ready + if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { + return; + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + readyList.resolveWith( document, [ jQuery ] ); + + // Trigger any bound ready events + if ( jQuery.fn.trigger ) { + jQuery( document ).trigger("ready").off("ready"); + } + } +}); + +/** + * The ready event handler and self cleanup method + */ +function completed() { + document.removeEventListener( "DOMContentLoaded", completed, false ); + window.removeEventListener( "load", completed, false ); + jQuery.ready(); +} + +jQuery.ready.promise = function( obj ) { + if ( !readyList ) { + + readyList = jQuery.Deferred(); + + // Catch cases where $(document).ready() is called after the browser event has already occurred. + // we once tried to use readyState "interactive" here, but it caused issues like the one + // discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15 + if ( document.readyState === "complete" ) { + // Handle it asynchronously to allow scripts the opportunity to delay ready + setTimeout( jQuery.ready ); + + } else { + + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", completed, false ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", completed, false ); + } + } + return readyList.promise( obj ); +}; + +// Kick off the DOM ready check even if the user does not +jQuery.ready.promise(); + + + + +// Multifunctional method to get and set values of a collection +// The value/s can optionally be executed if it's a function +var access = jQuery.access = function( elems, fn, key, value, chainable, emptyGet, raw ) { + var i = 0, + len = elems.length, + bulk = key == null; + + // Sets many values + if ( jQuery.type( key ) === "object" ) { + chainable = true; + for ( i in key ) { + jQuery.access( elems, fn, i, key[i], true, emptyGet, raw ); + } + + // Sets one value + } else if ( value !== undefined ) { + chainable = true; + + if ( !jQuery.isFunction( value ) ) { + raw = true; + } + + if ( bulk ) { + // Bulk operations run against the entire set + if ( raw ) { + fn.call( elems, value ); + fn = null; + + // ...except when executing function values + } else { + bulk = fn; + fn = function( elem, key, value ) { + return bulk.call( jQuery( elem ), value ); + }; + } + } + + if ( fn ) { + for ( ; i < len; i++ ) { + fn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) ); + } + } + } + + return chainable ? + elems : + + // Gets + bulk ? + fn.call( elems ) : + len ? fn( elems[0], key ) : emptyGet; +}; + + +/** + * Determines whether an object can have data + */ +jQuery.acceptData = function( owner ) { + // Accepts only: + // - Node + // - Node.ELEMENT_NODE + // - Node.DOCUMENT_NODE + // - Object + // - Any + /* jshint -W018 */ + return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); +}; + + +function Data() { + // Support: Android < 4, + // Old WebKit does not have Object.preventExtensions/freeze method, + // return new empty object instead with no [[set]] accessor + Object.defineProperty( this.cache = {}, 0, { + get: function() { + return {}; + } + }); + + this.expando = jQuery.expando + Math.random(); +} + +Data.uid = 1; +Data.accepts = jQuery.acceptData; + +Data.prototype = { + key: function( owner ) { + // We can accept data for non-element nodes in modern browsers, + // but we should not, see #8335. + // Always return the key for a frozen object. + if ( !Data.accepts( owner ) ) { + return 0; + } + + var descriptor = {}, + // Check if the owner object already has a cache key + unlock = owner[ this.expando ]; + + // If not, create one + if ( !unlock ) { + unlock = Data.uid++; + + // Secure it in a non-enumerable, non-writable property + try { + descriptor[ this.expando ] = { value: unlock }; + Object.defineProperties( owner, descriptor ); + + // Support: Android < 4 + // Fallback to a less secure definition + } catch ( e ) { + descriptor[ this.expando ] = unlock; + jQuery.extend( owner, descriptor ); + } + } + + // Ensure the cache object + if ( !this.cache[ unlock ] ) { + this.cache[ unlock ] = {}; + } + + return unlock; + }, + set: function( owner, data, value ) { + var prop, + // There may be an unlock assigned to this node, + // if there is no entry for this "owner", create one inline + // and set the unlock as though an owner entry had always existed + unlock = this.key( owner ), + cache = this.cache[ unlock ]; + + // Handle: [ owner, key, value ] args + if ( typeof data === "string" ) { + cache[ data ] = value; + + // Handle: [ owner, { properties } ] args + } else { + // Fresh assignments by object are shallow copied + if ( jQuery.isEmptyObject( cache ) ) { + jQuery.extend( this.cache[ unlock ], data ); + // Otherwise, copy the properties one-by-one to the cache object + } else { + for ( prop in data ) { + cache[ prop ] = data[ prop ]; + } + } + } + return cache; + }, + get: function( owner, key ) { + // Either a valid cache is found, or will be created. + // New caches will be created and the unlock returned, + // allowing direct access to the newly created + // empty data object. A valid owner object must be provided. + var cache = this.cache[ this.key( owner ) ]; + + return key === undefined ? + cache : cache[ key ]; + }, + access: function( owner, key, value ) { + var stored; + // In cases where either: + // + // 1. No key was specified + // 2. A string key was specified, but no value provided + // + // Take the "read" path and allow the get method to determine + // which value to return, respectively either: + // + // 1. The entire cache object + // 2. The data stored at the key + // + if ( key === undefined || + ((key && typeof key === "string") && value === undefined) ) { + + stored = this.get( owner, key ); + + return stored !== undefined ? + stored : this.get( owner, jQuery.camelCase(key) ); + } + + // [*]When the key is not a string, or both a key and value + // are specified, set or extend (existing objects) with either: + // + // 1. An object of properties + // 2. A key and value + // + this.set( owner, key, value ); + + // Since the "set" path can have two possible entry points + // return the expected data based on which path was taken[*] + return value !== undefined ? value : key; + }, + remove: function( owner, key ) { + var i, name, camel, + unlock = this.key( owner ), + cache = this.cache[ unlock ]; + + if ( key === undefined ) { + this.cache[ unlock ] = {}; + + } else { + // Support array or space separated string of keys + if ( jQuery.isArray( key ) ) { + // If "name" is an array of keys... + // When data is initially created, via ("key", "val") signature, + // keys will be converted to camelCase. + // Since there is no way to tell _how_ a key was added, remove + // both plain key and camelCase key. #12786 + // This will only penalize the array argument path. + name = key.concat( key.map( jQuery.camelCase ) ); + } else { + camel = jQuery.camelCase( key ); + // Try the string as a key before any manipulation + if ( key in cache ) { + name = [ key, camel ]; + } else { + // If a key with the spaces exists, use it. + // Otherwise, create an array by matching non-whitespace + name = camel; + name = name in cache ? + [ name ] : ( name.match( rnotwhite ) || [] ); + } + } + + i = name.length; + while ( i-- ) { + delete cache[ name[ i ] ]; + } + } + }, + hasData: function( owner ) { + return !jQuery.isEmptyObject( + this.cache[ owner[ this.expando ] ] || {} + ); + }, + discard: function( owner ) { + if ( owner[ this.expando ] ) { + delete this.cache[ owner[ this.expando ] ]; + } + } +}; +var data_priv = new Data(); + +var data_user = new Data(); + + + +/* + Implementation Summary + + 1. Enforce API surface and semantic compatibility with 1.9.x branch + 2. Improve the module's maintainability by reducing the storage + paths to a single mechanism. + 3. Use the same single mechanism to support "private" and "user" data. + 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) + 5. Avoid exposing implementation details on user objects (eg. expando properties) + 6. Provide a clear path for implementation upgrade to WeakMap in 2014 +*/ +var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, + rmultiDash = /([A-Z])/g; + +function dataAttr( elem, key, data ) { + var name; + + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase(); + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = data === "true" ? true : + data === "false" ? false : + data === "null" ? null : + // Only convert to a number if it doesn't change the string + +data + "" === data ? +data : + rbrace.test( data ) ? jQuery.parseJSON( data ) : + data; + } catch( e ) {} + + // Make sure we set the data so it isn't changed later + data_user.set( elem, key, data ); + } else { + data = undefined; + } + } + return data; +} + +jQuery.extend({ + hasData: function( elem ) { + return data_user.hasData( elem ) || data_priv.hasData( elem ); + }, + + data: function( elem, name, data ) { + return data_user.access( elem, name, data ); + }, + + removeData: function( elem, name ) { + data_user.remove( elem, name ); + }, + + // TODO: Now that all calls to _data and _removeData have been replaced + // with direct calls to data_priv methods, these can be deprecated. + _data: function( elem, name, data ) { + return data_priv.access( elem, name, data ); + }, + + _removeData: function( elem, name ) { + data_priv.remove( elem, name ); + } +}); + +jQuery.fn.extend({ + data: function( key, value ) { + var i, name, data, + elem = this[ 0 ], + attrs = elem && elem.attributes; + + // Gets all values + if ( key === undefined ) { + if ( this.length ) { + data = data_user.get( elem ); + + if ( elem.nodeType === 1 && !data_priv.get( elem, "hasDataAttrs" ) ) { + i = attrs.length; + while ( i-- ) { + name = attrs[ i ].name; + + if ( name.indexOf( "data-" ) === 0 ) { + name = jQuery.camelCase( name.slice(5) ); + dataAttr( elem, name, data[ name ] ); + } + } + data_priv.set( elem, "hasDataAttrs", true ); + } + } + + return data; + } + + // Sets multiple values + if ( typeof key === "object" ) { + return this.each(function() { + data_user.set( this, key ); + }); + } + + return access( this, function( value ) { + var data, + camelKey = jQuery.camelCase( key ); + + // The calling jQuery object (element matches) is not empty + // (and therefore has an element appears at this[ 0 ]) and the + // `value` parameter was not undefined. An empty jQuery object + // will result in `undefined` for elem = this[ 0 ] which will + // throw an exception if an attempt to read a data cache is made. + if ( elem && value === undefined ) { + // Attempt to get data from the cache + // with the key as-is + data = data_user.get( elem, key ); + if ( data !== undefined ) { + return data; + } + + // Attempt to get data from the cache + // with the key camelized + data = data_user.get( elem, camelKey ); + if ( data !== undefined ) { + return data; + } + + // Attempt to "discover" the data in + // HTML5 custom data-* attrs + data = dataAttr( elem, camelKey, undefined ); + if ( data !== undefined ) { + return data; + } + + // We tried really hard, but the data doesn't exist. + return; + } + + // Set the data... + this.each(function() { + // First, attempt to store a copy or reference of any + // data that might've been store with a camelCased key. + var data = data_user.get( this, camelKey ); + + // For HTML5 data-* attribute interop, we have to + // store property names with dashes in a camelCase form. + // This might not apply to all properties...* + data_user.set( this, camelKey, value ); + + // *... In the case of properties that might _actually_ + // have dashes, we need to also store a copy of that + // unchanged property. + if ( key.indexOf("-") !== -1 && data !== undefined ) { + data_user.set( this, key, value ); + } + }); + }, null, value, arguments.length > 1, null, true ); + }, + + removeData: function( key ) { + return this.each(function() { + data_user.remove( this, key ); + }); + } +}); + + +jQuery.extend({ + queue: function( elem, type, data ) { + var queue; + + if ( elem ) { + type = ( type || "fx" ) + "queue"; + queue = data_priv.get( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !queue || jQuery.isArray( data ) ) { + queue = data_priv.access( elem, type, jQuery.makeArray(data) ); + } else { + queue.push( data ); + } + } + return queue || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + startLength = queue.length, + fn = queue.shift(), + hooks = jQuery._queueHooks( elem, type ), + next = function() { + jQuery.dequeue( elem, type ); + }; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + startLength--; + } + + if ( fn ) { + + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift( "inprogress" ); + } + + // clear up the last queue stop function + delete hooks.stop; + fn.call( elem, next, hooks ); + } + + if ( !startLength && hooks ) { + hooks.empty.fire(); + } + }, + + // not intended for public consumption - generates a queueHooks object, or returns the current one + _queueHooks: function( elem, type ) { + var key = type + "queueHooks"; + return data_priv.get( elem, key ) || data_priv.access( elem, key, { + empty: jQuery.Callbacks("once memory").add(function() { + data_priv.remove( elem, [ type + "queue", key ] ); + }) + }); + } +}); + +jQuery.fn.extend({ + queue: function( type, data ) { + var setter = 2; + + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + setter--; + } + + if ( arguments.length < setter ) { + return jQuery.queue( this[0], type ); + } + + return data === undefined ? + this : + this.each(function() { + var queue = jQuery.queue( this, type, data ); + + // ensure a hooks for this queue + jQuery._queueHooks( this, type ); + + if ( type === "fx" && queue[0] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + }); + }, + dequeue: function( type ) { + return this.each(function() { + jQuery.dequeue( this, type ); + }); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, obj ) { + var tmp, + count = 1, + defer = jQuery.Deferred(), + elements = this, + i = this.length, + resolve = function() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + }; + + if ( typeof type !== "string" ) { + obj = type; + type = undefined; + } + type = type || "fx"; + + while ( i-- ) { + tmp = data_priv.get( elements[ i ], type + "queueHooks" ); + if ( tmp && tmp.empty ) { + count++; + tmp.empty.add( resolve ); + } + } + resolve(); + return defer.promise( obj ); + } +}); +var pnum = (/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/).source; + +var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; + +var isHidden = function( elem, el ) { + // isHidden might be called from jQuery#filter function; + // in that case, element will be second argument + elem = el || elem; + return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem ); + }; + +var rcheckableType = (/^(?:checkbox|radio)$/i); + + + +(function() { + var fragment = document.createDocumentFragment(), + div = fragment.appendChild( document.createElement( "div" ) ); + + // #11217 - WebKit loses check when the name is after the checked attribute + div.innerHTML = ""; + + // Support: Safari 5.1, iOS 5.1, Android 4.x, Android 2.3 + // old WebKit doesn't clone checked state correctly in fragments + support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Make sure textarea (and checkbox) defaultValue is properly cloned + // Support: IE9-IE11+ + div.innerHTML = ""; + support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; +})(); +var strundefined = typeof undefined; + + + +support.focusinBubbles = "onfocusin" in window; + + +var + rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|contextmenu)|click/, + rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + rtypenamespace = /^([^.]*)(?:\.(.+)|)$/; + +function returnTrue() { + return true; +} + +function returnFalse() { + return false; +} + +function safeActiveElement() { + try { + return document.activeElement; + } catch ( err ) { } +} + +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { + + global: {}, + + add: function( elem, types, handler, data, selector ) { + + var handleObjIn, eventHandle, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = data_priv.get( elem ); + + // Don't attach events to noData or text/comment nodes (but allow plain objects) + if ( !elemData ) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + if ( !(events = elemData.events) ) { + events = elemData.events = {}; + } + if ( !(eventHandle = elemData.handle) ) { + eventHandle = elemData.handle = function( e ) { + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== strundefined && jQuery.event.triggered !== e.type ? + jQuery.event.dispatch.apply( elem, arguments ) : undefined; + }; + } + + // Handle multiple events separated by a space + types = ( types || "" ).match( rnotwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[t] ) || []; + type = origType = tmp[1]; + namespaces = ( tmp[2] || "" ).split( "." ).sort(); + + // There *must* be a type, no attaching namespace-only handlers + if ( !type ) { + continue; + } + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; + + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend({ + type: type, + origType: origType, + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + needsContext: selector && jQuery.expr.match.needsContext.test( selector ), + namespace: namespaces.join(".") + }, handleObjIn ); + + // Init the event handler queue if we're the first + if ( !(handlers = events[ type ]) ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener if the special events handler returns false + if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle, false ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[ type ] = true; + } + + }, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + + var j, origCount, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = data_priv.hasData( elem ) && data_priv.get( elem ); + + if ( !elemData || !(events = elemData.events) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = ( types || "" ).match( rnotwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[t] ) || []; + type = origType = tmp[1]; + namespaces = ( tmp[2] || "" ).split( "." ).sort(); + + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector ? special.delegateType : special.bindType ) || type; + handlers = events[ type ] || []; + tmp = tmp[2] && new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ); + + // Remove matching events + origCount = j = handlers.length; + while ( j-- ) { + handleObj = handlers[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !tmp || tmp.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) { + handlers.splice( j, 1 ); + + if ( handleObj.selector ) { + handlers.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( origCount && !handlers.length ) { + if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) { + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + delete elemData.handle; + data_priv.remove( elem, "events" ); + } + }, + + trigger: function( event, data, elem, onlyHandlers ) { + + var i, cur, tmp, bubbleType, ontype, handle, special, + eventPath = [ elem || document ], + type = hasOwn.call( event, "type" ) ? event.type : event, + namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split(".") : []; + + cur = tmp = elem = elem || document; + + // Don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + if ( type.indexOf(".") >= 0 ) { + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split("."); + type = namespaces.shift(); + namespaces.sort(); + } + ontype = type.indexOf(":") < 0 && "on" + type; + + // Caller can pass in a jQuery.Event object, Object, or just an event type string + event = event[ jQuery.expando ] ? + event : + new jQuery.Event( type, typeof event === "object" && event ); + + // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) + event.isTrigger = onlyHandlers ? 2 : 3; + event.namespace = namespaces.join("."); + event.namespace_re = event.namespace ? + new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ) : + null; + + // Clean up the event in case it is being reused + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data == null ? + [ event ] : + jQuery.makeArray( data, [ event ] ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + if ( !rfocusMorph.test( bubbleType + type ) ) { + cur = cur.parentNode; + } + for ( ; cur; cur = cur.parentNode ) { + eventPath.push( cur ); + tmp = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( tmp === (elem.ownerDocument || document) ) { + eventPath.push( tmp.defaultView || tmp.parentWindow || window ); + } + } + + // Fire handlers on the event path + i = 0; + while ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) { + + event.type = i > 1 ? + bubbleType : + special.bindType || type; + + // jQuery handler + handle = ( data_priv.get( cur, "events" ) || {} )[ event.type ] && data_priv.get( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + + // Native handler + handle = ontype && cur[ ontype ]; + if ( handle && handle.apply && jQuery.acceptData( cur ) ) { + event.result = handle.apply( cur, data ); + if ( event.result === false ) { + event.preventDefault(); + } + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( (!special._default || special._default.apply( eventPath.pop(), data ) === false) && + jQuery.acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name name as the event. + // Don't do default actions on window, that's where global variables be (#6170) + if ( ontype && jQuery.isFunction( elem[ type ] ) && !jQuery.isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + tmp = elem[ ontype ]; + + if ( tmp ) { + elem[ ontype ] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + elem[ type ](); + jQuery.event.triggered = undefined; + + if ( tmp ) { + elem[ ontype ] = tmp; + } + } + } + } + + return event.result; + }, + + dispatch: function( event ) { + + // Make a writable jQuery.Event from the native event object + event = jQuery.event.fix( event ); + + var i, j, ret, matched, handleObj, + handlerQueue = [], + args = slice.call( arguments ), + handlers = ( data_priv.get( this, "events" ) || {} )[ event.type ] || [], + special = jQuery.event.special[ event.type ] || {}; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[0] = event; + event.delegateTarget = this; + + // Call the preDispatch hook for the mapped type, and let it bail if desired + if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { + return; + } + + // Determine handlers + handlerQueue = jQuery.event.handlers.call( this, event, handlers ); + + // Run delegates first; they may want to stop propagation beneath us + i = 0; + while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) { + event.currentTarget = matched.elem; + + j = 0; + while ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) { + + // Triggered event must either 1) have no namespace, or + // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace). + if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) { + + event.handleObj = handleObj; + event.data = handleObj.data; + + ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler ) + .apply( matched.elem, args ); + + if ( ret !== undefined ) { + if ( (event.result = ret) === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + // Call the postDispatch hook for the mapped type + if ( special.postDispatch ) { + special.postDispatch.call( this, event ); + } + + return event.result; + }, + + handlers: function( event, handlers ) { + var i, matches, sel, handleObj, + handlerQueue = [], + delegateCount = handlers.delegateCount, + cur = event.target; + + // Find delegate handlers + // Black-hole SVG instance trees (#13180) + // Avoid non-left-click bubbling in Firefox (#3861) + if ( delegateCount && cur.nodeType && (!event.button || event.type !== "click") ) { + + for ( ; cur !== this; cur = cur.parentNode || this ) { + + // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) + if ( cur.disabled !== true || event.type !== "click" ) { + matches = []; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + + // Don't conflict with Object.prototype properties (#13203) + sel = handleObj.selector + " "; + + if ( matches[ sel ] === undefined ) { + matches[ sel ] = handleObj.needsContext ? + jQuery( sel, this ).index( cur ) >= 0 : + jQuery.find( sel, this, null, [ cur ] ).length; + } + if ( matches[ sel ] ) { + matches.push( handleObj ); + } + } + if ( matches.length ) { + handlerQueue.push({ elem: cur, handlers: matches }); + } + } + } + } + + // Add the remaining (directly-bound) handlers + if ( delegateCount < handlers.length ) { + handlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) }); + } + + return handlerQueue; + }, + + // Includes some event props shared by KeyEvent and MouseEvent + props: "altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "), + + fixHooks: {}, + + keyHooks: { + props: "char charCode key keyCode".split(" "), + filter: function( event, original ) { + + // Add which for key events + if ( event.which == null ) { + event.which = original.charCode != null ? original.charCode : original.keyCode; + } + + return event; + } + }, + + mouseHooks: { + props: "button buttons clientX clientY offsetX offsetY pageX pageY screenX screenY toElement".split(" "), + filter: function( event, original ) { + var eventDoc, doc, body, + button = original.button; + + // Calculate pageX/Y if missing and clientX/Y available + if ( event.pageX == null && original.clientX != null ) { + eventDoc = event.target.ownerDocument || document; + doc = eventDoc.documentElement; + body = eventDoc.body; + + event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 ); + event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 ); + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + // Note: button is not normalized, so don't use it + if ( !event.which && button !== undefined ) { + event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) ); + } + + return event; + } + }, + + fix: function( event ) { + if ( event[ jQuery.expando ] ) { + return event; + } + + // Create a writable copy of the event object and normalize some properties + var i, prop, copy, + type = event.type, + originalEvent = event, + fixHook = this.fixHooks[ type ]; + + if ( !fixHook ) { + this.fixHooks[ type ] = fixHook = + rmouseEvent.test( type ) ? this.mouseHooks : + rkeyEvent.test( type ) ? this.keyHooks : + {}; + } + copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props; + + event = new jQuery.Event( originalEvent ); + + i = copy.length; + while ( i-- ) { + prop = copy[ i ]; + event[ prop ] = originalEvent[ prop ]; + } + + // Support: Cordova 2.5 (WebKit) (#13255) + // All events should have a target; Cordova deviceready doesn't + if ( !event.target ) { + event.target = document; + } + + // Support: Safari 6.0+, Chrome < 28 + // Target should not be a text node (#504, #13143) + if ( event.target.nodeType === 3 ) { + event.target = event.target.parentNode; + } + + return fixHook.filter ? fixHook.filter( event, originalEvent ) : event; + }, + + special: { + load: { + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + focus: { + // Fire native event if possible so blur/focus sequence is correct + trigger: function() { + if ( this !== safeActiveElement() && this.focus ) { + this.focus(); + return false; + } + }, + delegateType: "focusin" + }, + blur: { + trigger: function() { + if ( this === safeActiveElement() && this.blur ) { + this.blur(); + return false; + } + }, + delegateType: "focusout" + }, + click: { + // For checkbox, fire native event so checked state will be right + trigger: function() { + if ( this.type === "checkbox" && this.click && jQuery.nodeName( this, "input" ) ) { + this.click(); + return false; + } + }, + + // For cross-browser consistency, don't fire native .click() on links + _default: function( event ) { + return jQuery.nodeName( event.target, "a" ); + } + }, + + beforeunload: { + postDispatch: function( event ) { + + // Support: Firefox 20+ + // Firefox doesn't alert if the returnValue field is not set. + if ( event.result !== undefined ) { + event.originalEvent.returnValue = event.result; + } + } + } + }, + + simulate: function( type, elem, event, bubble ) { + // Piggyback on a donor event to simulate a different one. + // Fake originalEvent to avoid donor's stopPropagation, but if the + // simulated event prevents default then we do the same on the donor. + var e = jQuery.extend( + new jQuery.Event(), + event, + { + type: type, + isSimulated: true, + originalEvent: {} + } + ); + if ( bubble ) { + jQuery.event.trigger( e, null, elem ); + } else { + jQuery.event.dispatch.call( elem, e ); + } + if ( e.isDefaultPrevented() ) { + event.preventDefault(); + } + } +}; + +jQuery.removeEvent = function( elem, type, handle ) { + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle, false ); + } +}; + +jQuery.Event = function( src, props ) { + // Allow instantiation without the 'new' keyword + if ( !(this instanceof jQuery.Event) ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = src.defaultPrevented || + // Support: Android < 4.0 + src.defaultPrevented === undefined && + src.getPreventDefault && src.getPreventDefault() ? + returnTrue : + returnFalse; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || jQuery.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse, + + preventDefault: function() { + var e = this.originalEvent; + + this.isDefaultPrevented = returnTrue; + + if ( e && e.preventDefault ) { + e.preventDefault(); + } + }, + stopPropagation: function() { + var e = this.originalEvent; + + this.isPropagationStopped = returnTrue; + + if ( e && e.stopPropagation ) { + e.stopPropagation(); + } + }, + stopImmediatePropagation: function() { + this.isImmediatePropagationStopped = returnTrue; + this.stopPropagation(); + } +}; + +// Create mouseenter/leave events using mouseover/out and event-time checks +// Support: Chrome 15+ +jQuery.each({ + mouseenter: "mouseover", + mouseleave: "mouseout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var ret, + target = this, + related = event.relatedTarget, + handleObj = event.handleObj; + + // For mousenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || (related !== target && !jQuery.contains( target, related )) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +}); + +// Create "bubbling" focus and blur events +// Support: Firefox, Chrome, Safari +if ( !support.focusinBubbles ) { + jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler on the document while someone wants focusin/focusout + var handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true ); + }; + + jQuery.event.special[ fix ] = { + setup: function() { + var doc = this.ownerDocument || this, + attaches = data_priv.access( doc, fix ); + + if ( !attaches ) { + doc.addEventListener( orig, handler, true ); + } + data_priv.access( doc, fix, ( attaches || 0 ) + 1 ); + }, + teardown: function() { + var doc = this.ownerDocument || this, + attaches = data_priv.access( doc, fix ) - 1; + + if ( !attaches ) { + doc.removeEventListener( orig, handler, true ); + data_priv.remove( doc, fix ); + + } else { + data_priv.access( doc, fix, attaches ); + } + } + }; + }); +} + +jQuery.fn.extend({ + + on: function( types, selector, data, fn, /*INTERNAL*/ one ) { + var origFn, type; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { + // ( types-Object, data ) + data = data || selector; + selector = undefined; + } + for ( type in types ) { + this.on( type, selector, data, types[ type ], one ); + } + return this; + } + + if ( data == null && fn == null ) { + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return this; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return this.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + }); + }, + one: function( types, selector, data, fn ) { + return this.on( types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + var handleObj, type; + if ( types && types.preventDefault && types.handleObj ) { + // ( event ) dispatched jQuery.Event + handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + // ( types-object [, selector] ) + for ( type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each(function() { + jQuery.event.remove( this, types, fn, selector ); + }); + }, + + trigger: function( type, data ) { + return this.each(function() { + jQuery.event.trigger( type, data, this ); + }); + }, + triggerHandler: function( type, data ) { + var elem = this[0]; + if ( elem ) { + return jQuery.event.trigger( type, data, elem, true ); + } + } +}); + + +var + rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi, + rtagName = /<([\w:]+)/, + rhtml = /<|&#?\w+;/, + rnoInnerhtml = /<(?:script|style|link)/i, + // checked="checked" or checked + rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i, + rscriptType = /^$|\/(?:java|ecma)script/i, + rscriptTypeMasked = /^true\/(.*)/, + rcleanScript = /^\s*\s*$/g, + + // We have to close these tags to support XHTML (#13200) + wrapMap = { + + // Support: IE 9 + option: [ 1, "" ], + + thead: [ 1, "", "
" ], + col: [ 2, "", "
" ], + tr: [ 2, "", "
" ], + td: [ 3, "", "
" ], + + _default: [ 0, "", "" ] + }; + +// Support: IE 9 +wrapMap.optgroup = wrapMap.option; + +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +// Support: 1.x compatibility +// Manipulating tables requires a tbody +function manipulationTarget( elem, content ) { + return jQuery.nodeName( elem, "table" ) && + jQuery.nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ? + + elem.getElementsByTagName("tbody")[0] || + elem.appendChild( elem.ownerDocument.createElement("tbody") ) : + elem; +} + +// Replace/restore the type attribute of script elements for safe DOM manipulation +function disableScript( elem ) { + elem.type = (elem.getAttribute("type") !== null) + "/" + elem.type; + return elem; +} +function restoreScript( elem ) { + var match = rscriptTypeMasked.exec( elem.type ); + + if ( match ) { + elem.type = match[ 1 ]; + } else { + elem.removeAttribute("type"); + } + + return elem; +} + +// Mark scripts as having already been evaluated +function setGlobalEval( elems, refElements ) { + var i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + data_priv.set( + elems[ i ], "globalEval", !refElements || data_priv.get( refElements[ i ], "globalEval" ) + ); + } +} + +function cloneCopyEvent( src, dest ) { + var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events; + + if ( dest.nodeType !== 1 ) { + return; + } + + // 1. Copy private data: events, handlers, etc. + if ( data_priv.hasData( src ) ) { + pdataOld = data_priv.access( src ); + pdataCur = data_priv.set( dest, pdataOld ); + events = pdataOld.events; + + if ( events ) { + delete pdataCur.handle; + pdataCur.events = {}; + + for ( type in events ) { + for ( i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( dest, type, events[ type ][ i ] ); + } + } + } + } + + // 2. Copy user data + if ( data_user.hasData( src ) ) { + udataOld = data_user.access( src ); + udataCur = jQuery.extend( {}, udataOld ); + + data_user.set( dest, udataCur ); + } +} + +function getAll( context, tag ) { + var ret = context.getElementsByTagName ? context.getElementsByTagName( tag || "*" ) : + context.querySelectorAll ? context.querySelectorAll( tag || "*" ) : + []; + + return tag === undefined || tag && jQuery.nodeName( context, tag ) ? + jQuery.merge( [ context ], ret ) : + ret; +} + +// Support: IE >= 9 +function fixInput( src, dest ) { + var nodeName = dest.nodeName.toLowerCase(); + + // Fails to persist the checked state of a cloned checkbox or radio button. + if ( nodeName === "input" && rcheckableType.test( src.type ) ) { + dest.checked = src.checked; + + // Fails to return the selected option to the default selected state when cloning options + } else if ( nodeName === "input" || nodeName === "textarea" ) { + dest.defaultValue = src.defaultValue; + } +} + +jQuery.extend({ + clone: function( elem, dataAndEvents, deepDataAndEvents ) { + var i, l, srcElements, destElements, + clone = elem.cloneNode( true ), + inPage = jQuery.contains( elem.ownerDocument, elem ); + + // Support: IE >= 9 + // Fix Cloning issues + if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && + !jQuery.isXMLDoc( elem ) ) { + + // We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2 + destElements = getAll( clone ); + srcElements = getAll( elem ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + fixInput( srcElements[ i ], destElements[ i ] ); + } + } + + // Copy the events from the original to the clone + if ( dataAndEvents ) { + if ( deepDataAndEvents ) { + srcElements = srcElements || getAll( elem ); + destElements = destElements || getAll( clone ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + cloneCopyEvent( srcElements[ i ], destElements[ i ] ); + } + } else { + cloneCopyEvent( elem, clone ); + } + } + + // Preserve script evaluation history + destElements = getAll( clone, "script" ); + if ( destElements.length > 0 ) { + setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); + } + + // Return the cloned set + return clone; + }, + + buildFragment: function( elems, context, scripts, selection ) { + var elem, tmp, tag, wrap, contains, j, + fragment = context.createDocumentFragment(), + nodes = [], + i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + elem = elems[ i ]; + + if ( elem || elem === 0 ) { + + // Add nodes directly + if ( jQuery.type( elem ) === "object" ) { + // Support: QtWebKit + // jQuery.merge because push.apply(_, arraylike) throws + jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); + + // Convert non-html into a text node + } else if ( !rhtml.test( elem ) ) { + nodes.push( context.createTextNode( elem ) ); + + // Convert html into DOM nodes + } else { + tmp = tmp || fragment.appendChild( context.createElement("div") ); + + // Deserialize a standard representation + tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); + wrap = wrapMap[ tag ] || wrapMap._default; + tmp.innerHTML = wrap[ 1 ] + elem.replace( rxhtmlTag, "<$1>" ) + wrap[ 2 ]; + + // Descend through wrappers to the right content + j = wrap[ 0 ]; + while ( j-- ) { + tmp = tmp.lastChild; + } + + // Support: QtWebKit + // jQuery.merge because push.apply(_, arraylike) throws + jQuery.merge( nodes, tmp.childNodes ); + + // Remember the top-level container + tmp = fragment.firstChild; + + // Fixes #12346 + // Support: Webkit, IE + tmp.textContent = ""; + } + } + } + + // Remove wrapper from fragment + fragment.textContent = ""; + + i = 0; + while ( (elem = nodes[ i++ ]) ) { + + // #4087 - If origin and destination elements are the same, and this is + // that element, do not do anything + if ( selection && jQuery.inArray( elem, selection ) !== -1 ) { + continue; + } + + contains = jQuery.contains( elem.ownerDocument, elem ); + + // Append to fragment + tmp = getAll( fragment.appendChild( elem ), "script" ); + + // Preserve script evaluation history + if ( contains ) { + setGlobalEval( tmp ); + } + + // Capture executables + if ( scripts ) { + j = 0; + while ( (elem = tmp[ j++ ]) ) { + if ( rscriptType.test( elem.type || "" ) ) { + scripts.push( elem ); + } + } + } + } + + return fragment; + }, + + cleanData: function( elems ) { + var data, elem, events, type, key, j, + special = jQuery.event.special, + i = 0; + + for ( ; (elem = elems[ i ]) !== undefined; i++ ) { + if ( jQuery.acceptData( elem ) ) { + key = elem[ data_priv.expando ]; + + if ( key && (data = data_priv.cache[ key ]) ) { + events = Object.keys( data.events || {} ); + if ( events.length ) { + for ( j = 0; (type = events[j]) !== undefined; j++ ) { + if ( special[ type ] ) { + jQuery.event.remove( elem, type ); + + // This is a shortcut to avoid jQuery.event.remove's overhead + } else { + jQuery.removeEvent( elem, type, data.handle ); + } + } + } + if ( data_priv.cache[ key ] ) { + // Discard any remaining `private` data + delete data_priv.cache[ key ]; + } + } + } + // Discard any remaining `user` data + delete data_user.cache[ elem[ data_user.expando ] ]; + } + } +}); + +jQuery.fn.extend({ + text: function( value ) { + return access( this, function( value ) { + return value === undefined ? + jQuery.text( this ) : + this.empty().each(function() { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + this.textContent = value; + } + }); + }, null, value, arguments.length ); + }, + + append: function() { + return this.domManip( arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.appendChild( elem ); + } + }); + }, + + prepend: function() { + return this.domManip( arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.insertBefore( elem, target.firstChild ); + } + }); + }, + + before: function() { + return this.domManip( arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this ); + } + }); + }, + + after: function() { + return this.domManip( arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + } + }); + }, + + remove: function( selector, keepData /* Internal Use Only */ ) { + var elem, + elems = selector ? jQuery.filter( selector, this ) : this, + i = 0; + + for ( ; (elem = elems[i]) != null; i++ ) { + if ( !keepData && elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem ) ); + } + + if ( elem.parentNode ) { + if ( keepData && jQuery.contains( elem.ownerDocument, elem ) ) { + setGlobalEval( getAll( elem, "script" ) ); + } + elem.parentNode.removeChild( elem ); + } + } + + return this; + }, + + empty: function() { + var elem, + i = 0; + + for ( ; (elem = this[i]) != null; i++ ) { + if ( elem.nodeType === 1 ) { + + // Prevent memory leaks + jQuery.cleanData( getAll( elem, false ) ); + + // Remove any remaining nodes + elem.textContent = ""; + } + } + + return this; + }, + + clone: function( dataAndEvents, deepDataAndEvents ) { + dataAndEvents = dataAndEvents == null ? false : dataAndEvents; + deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; + + return this.map(function() { + return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); + }); + }, + + html: function( value ) { + return access( this, function( value ) { + var elem = this[ 0 ] || {}, + i = 0, + l = this.length; + + if ( value === undefined && elem.nodeType === 1 ) { + return elem.innerHTML; + } + + // See if we can take a shortcut and just use innerHTML + if ( typeof value === "string" && !rnoInnerhtml.test( value ) && + !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { + + value = value.replace( rxhtmlTag, "<$1>" ); + + try { + for ( ; i < l; i++ ) { + elem = this[ i ] || {}; + + // Remove element nodes and prevent memory leaks + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + elem.innerHTML = value; + } + } + + elem = 0; + + // If using innerHTML throws an exception, use the fallback method + } catch( e ) {} + } + + if ( elem ) { + this.empty().append( value ); + } + }, null, value, arguments.length ); + }, + + replaceWith: function() { + var arg = arguments[ 0 ]; + + // Make the changes, replacing each context element with the new content + this.domManip( arguments, function( elem ) { + arg = this.parentNode; + + jQuery.cleanData( getAll( this ) ); + + if ( arg ) { + arg.replaceChild( elem, this ); + } + }); + + // Force removal if there was no new content (e.g., from empty arguments) + return arg && (arg.length || arg.nodeType) ? this : this.remove(); + }, + + detach: function( selector ) { + return this.remove( selector, true ); + }, + + domManip: function( args, callback ) { + + // Flatten any nested arrays + args = concat.apply( [], args ); + + var fragment, first, scripts, hasScripts, node, doc, + i = 0, + l = this.length, + set = this, + iNoClone = l - 1, + value = args[ 0 ], + isFunction = jQuery.isFunction( value ); + + // We can't cloneNode fragments that contain checked, in WebKit + if ( isFunction || + ( l > 1 && typeof value === "string" && + !support.checkClone && rchecked.test( value ) ) ) { + return this.each(function( index ) { + var self = set.eq( index ); + if ( isFunction ) { + args[ 0 ] = value.call( this, index, self.html() ); + } + self.domManip( args, callback ); + }); + } + + if ( l ) { + fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, this ); + first = fragment.firstChild; + + if ( fragment.childNodes.length === 1 ) { + fragment = first; + } + + if ( first ) { + scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); + hasScripts = scripts.length; + + // Use the original fragment for the last item instead of the first because it can end up + // being emptied incorrectly in certain situations (#8070). + for ( ; i < l; i++ ) { + node = fragment; + + if ( i !== iNoClone ) { + node = jQuery.clone( node, true, true ); + + // Keep references to cloned scripts for later restoration + if ( hasScripts ) { + // Support: QtWebKit + // jQuery.merge because push.apply(_, arraylike) throws + jQuery.merge( scripts, getAll( node, "script" ) ); + } + } + + callback.call( this[ i ], node, i ); + } + + if ( hasScripts ) { + doc = scripts[ scripts.length - 1 ].ownerDocument; + + // Reenable scripts + jQuery.map( scripts, restoreScript ); + + // Evaluate executable scripts on first document insertion + for ( i = 0; i < hasScripts; i++ ) { + node = scripts[ i ]; + if ( rscriptType.test( node.type || "" ) && + !data_priv.access( node, "globalEval" ) && jQuery.contains( doc, node ) ) { + + if ( node.src ) { + // Optional AJAX dependency, but won't run scripts if not present + if ( jQuery._evalUrl ) { + jQuery._evalUrl( node.src ); + } + } else { + jQuery.globalEval( node.textContent.replace( rcleanScript, "" ) ); + } + } + } + } + } + } + + return this; + } +}); + +jQuery.each({ + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var elems, + ret = [], + insert = jQuery( selector ), + last = insert.length - 1, + i = 0; + + for ( ; i <= last; i++ ) { + elems = i === last ? this : this.clone( true ); + jQuery( insert[ i ] )[ original ]( elems ); + + // Support: QtWebKit + // .get() because push.apply(_, arraylike) throws + push.apply( ret, elems.get() ); + } + + return this.pushStack( ret ); + }; +}); + + +var iframe, + elemdisplay = {}; + +/** + * Retrieve the actual display of a element + * @param {String} name nodeName of the element + * @param {Object} doc Document object + */ +// Called only from within defaultDisplay +function actualDisplay( name, doc ) { + var elem = jQuery( doc.createElement( name ) ).appendTo( doc.body ), + + // getDefaultComputedStyle might be reliably used only on attached element + display = window.getDefaultComputedStyle ? + + // Use of this method is a temporary fix (more like optmization) until something better comes along, + // since it was removed from specification and supported only in FF + window.getDefaultComputedStyle( elem[ 0 ] ).display : jQuery.css( elem[ 0 ], "display" ); + + // We don't have any data stored on the element, + // so use "detach" method as fast way to get rid of the element + elem.detach(); + + return display; +} + +/** + * Try to determine the default display value of an element + * @param {String} nodeName + */ +function defaultDisplay( nodeName ) { + var doc = document, + display = elemdisplay[ nodeName ]; + + if ( !display ) { + display = actualDisplay( nodeName, doc ); + + // If the simple way fails, read from inside an iframe + if ( display === "none" || !display ) { + + // Use the already-created iframe if possible + iframe = (iframe || jQuery( "