--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
+ <attributes>
+ <attribute name="annotationpath" value="/org.lttng.scope.common.core/annotations"/>
+ </attributes>
+ </classpathentry>
+ <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins">
+ <attributes>
+ <attribute name="annotationpath" value="/org.lttng.scope.common.core/annotations"/>
+ </attributes>
+ </classpathentry>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>org.lttng.scope.tmf2.views.core</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.ManifestBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.SchemaBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.api.tools.apiAnalysisBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.pde.PluginNature</nature>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ <nature>org.eclipse.pde.api.tools.apiAnalysisNature</nature>
+ </natures>
+</projectDescription>
--- /dev/null
+eclipse.preferences.version=1
+encoding/<project>=UTF-8
--- /dev/null
+eclipse.preferences.version=1
+line.separator=\n
--- /dev/null
+eclipse.preferences.version=1
+org.eclipse.jdt.core.codeComplete.argumentPrefixes=
+org.eclipse.jdt.core.codeComplete.argumentSuffixes=
+org.eclipse.jdt.core.codeComplete.fieldPrefixes=f
+org.eclipse.jdt.core.codeComplete.fieldSuffixes=
+org.eclipse.jdt.core.codeComplete.localPrefixes=
+org.eclipse.jdt.core.codeComplete.localSuffixes=
+org.eclipse.jdt.core.codeComplete.staticFieldPrefixes=
+org.eclipse.jdt.core.codeComplete.staticFieldSuffixes=
+org.eclipse.jdt.core.codeComplete.staticFinalFieldPrefixes=
+org.eclipse.jdt.core.codeComplete.staticFinalFieldSuffixes=
+org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=enabled
+org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=info
+org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull
+org.eclipse.jdt.core.compiler.annotation.nonnull.secondary=
+org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault
+org.eclipse.jdt.core.compiler.annotation.nonnullbydefault.secondary=
+org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable
+org.eclipse.jdt.core.compiler.annotation.nullable.secondary=
+org.eclipse.jdt.core.compiler.annotation.nullanalysis=enabled
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.8
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.doc.comment.support=enabled
+org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=error
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
+org.eclipse.jdt.core.compiler.problem.comparingIdentical=error
+org.eclipse.jdt.core.compiler.problem.deadCode=error
+org.eclipse.jdt.core.compiler.problem.deprecation=warning
+org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
+org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=enabled
+org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
+org.eclipse.jdt.core.compiler.problem.emptyStatement=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=error
+org.eclipse.jdt.core.compiler.problem.fallthroughCase=error
+org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled
+org.eclipse.jdt.core.compiler.problem.fieldHiding=error
+org.eclipse.jdt.core.compiler.problem.finalParameterBound=error
+org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=error
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=error
+org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=error
+org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=disabled
+org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=error
+org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=error
+org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=error
+org.eclipse.jdt.core.compiler.problem.invalidJavadoc=warning
+org.eclipse.jdt.core.compiler.problem.invalidJavadocTags=enabled
+org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsDeprecatedRef=enabled
+org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsNotVisibleRef=enabled
+org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsVisibility=protected
+org.eclipse.jdt.core.compiler.problem.localVariableHiding=error
+org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=error
+org.eclipse.jdt.core.compiler.problem.missingDefaultCase=error
+org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=error
+org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=enabled
+org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=error
+org.eclipse.jdt.core.compiler.problem.missingJavadocComments=warning
+org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsVisibility=protected
+org.eclipse.jdt.core.compiler.problem.missingJavadocTagDescription=all_standard_tags
+org.eclipse.jdt.core.compiler.problem.missingJavadocTags=warning
+org.eclipse.jdt.core.compiler.problem.missingJavadocTagsMethodTypeParameters=disabled
+org.eclipse.jdt.core.compiler.problem.missingJavadocTagsOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=protected
+org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=error
+org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled
+org.eclipse.jdt.core.compiler.problem.missingSerialVersion=error
+org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=error
+org.eclipse.jdt.core.compiler.problem.noEffectAssignment=error
+org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=error
+org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=warning
+org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning
+org.eclipse.jdt.core.compiler.problem.nonnullTypeVariableFromLegacyInvocation=info
+org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=warning
+org.eclipse.jdt.core.compiler.problem.nullReference=error
+org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error
+org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning
+org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=error
+org.eclipse.jdt.core.compiler.problem.parameterAssignment=error
+org.eclipse.jdt.core.compiler.problem.pessimisticNullAnalysisForFreeTypeVariables=warning
+org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=error
+org.eclipse.jdt.core.compiler.problem.potentialNullReference=error
+org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=warning
+org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning
+org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning
+org.eclipse.jdt.core.compiler.problem.redundantNullCheck=error
+org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=error
+org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=error
+org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore
+org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=error
+org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled
+org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=error
+org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled
+org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
+org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=enabled
+org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
+org.eclipse.jdt.core.compiler.problem.typeParameterHiding=error
+org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=disabled
+org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
+org.eclipse.jdt.core.compiler.problem.unclosedCloseable=error
+org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore
+org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=error
+org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentType=warning
+org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentTypeStrict=disabled
+org.eclipse.jdt.core.compiler.problem.unlikelyEqualsArgumentType=info
+org.eclipse.jdt.core.compiler.problem.unnecessaryElse=error
+org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=error
+org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=warning
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=disabled
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=disabled
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore
+org.eclipse.jdt.core.compiler.problem.unusedImport=warning
+org.eclipse.jdt.core.compiler.problem.unusedLabel=error
+org.eclipse.jdt.core.compiler.problem.unusedLocal=error
+org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=ignore
+org.eclipse.jdt.core.compiler.problem.unusedParameter=warning
+org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled
+org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning
+org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=warning
+org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=error
+org.eclipse.jdt.core.compiler.source=1.8
+org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines=2147483647
+org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_assignment=0
+org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
+org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80
+org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0
+org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
+org.eclipse.jdt.core.formatter.alignment_for_expressions_in_for_loop_header=0
+org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0
+org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
+org.eclipse.jdt.core.formatter.alignment_for_parameterized_type_references=0
+org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=80
+org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
+org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_type_arguments=0
+org.eclipse.jdt.core.formatter.alignment_for_type_parameters=0
+org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16
+org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
+org.eclipse.jdt.core.formatter.blank_lines_after_package=1
+org.eclipse.jdt.core.formatter.blank_lines_before_field=0
+org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
+org.eclipse.jdt.core.formatter.blank_lines_before_imports=1
+org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1
+org.eclipse.jdt.core.formatter.blank_lines_before_method=1
+org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
+org.eclipse.jdt.core.formatter.blank_lines_before_package=0
+org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
+org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1
+org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_lambda_body=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
+org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
+org.eclipse.jdt.core.formatter.comment.format_block_comments=true
+org.eclipse.jdt.core.formatter.comment.format_header=false
+org.eclipse.jdt.core.formatter.comment.format_html=true
+org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
+org.eclipse.jdt.core.formatter.comment.format_line_comments=true
+org.eclipse.jdt.core.formatter.comment.format_source_code=true
+org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true
+org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
+org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
+org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=insert
+org.eclipse.jdt.core.formatter.comment.line_length=80
+org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true
+org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true
+org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false
+org.eclipse.jdt.core.formatter.compact_else_if=true
+org.eclipse.jdt.core.formatter.continuation_indentation=2
+org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
+org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off
+org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on
+org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
+org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
+org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
+org.eclipse.jdt.core.formatter.indent_empty_lines=false
+org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
+org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
+org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
+org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=false
+org.eclipse.jdt.core.formatter.indentation.size=4
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_enum_constant=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
+org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
+org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow=insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert
+org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
+org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
+org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
+org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
+org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.join_lines_in_comments=true
+org.eclipse.jdt.core.formatter.join_wrapped_lines=false
+org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
+org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
+org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false
+org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
+org.eclipse.jdt.core.formatter.lineSplit=250
+org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
+org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
+org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
+org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1
+org.eclipse.jdt.core.formatter.parentheses_positions_in_annotation=common_lines
+org.eclipse.jdt.core.formatter.parentheses_positions_in_catch_clause=common_lines
+org.eclipse.jdt.core.formatter.parentheses_positions_in_enum_constant_declaration=common_lines
+org.eclipse.jdt.core.formatter.parentheses_positions_in_for_statment=common_lines
+org.eclipse.jdt.core.formatter.parentheses_positions_in_if_while_statement=common_lines
+org.eclipse.jdt.core.formatter.parentheses_positions_in_lambda_declaration=common_lines
+org.eclipse.jdt.core.formatter.parentheses_positions_in_method_delcaration=common_lines
+org.eclipse.jdt.core.formatter.parentheses_positions_in_method_invocation=common_lines
+org.eclipse.jdt.core.formatter.parentheses_positions_in_switch_statement=common_lines
+org.eclipse.jdt.core.formatter.parentheses_positions_in_try_clause=common_lines
+org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true
+org.eclipse.jdt.core.formatter.tabulation.char=space
+org.eclipse.jdt.core.formatter.tabulation.size=4
+org.eclipse.jdt.core.formatter.use_on_off_tags=false
+org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
+org.eclipse.jdt.core.formatter.wrap_before_assignment_operator=false
+org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
+org.eclipse.jdt.core.formatter.wrap_before_conditional_operator=true
+org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true
+org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true
+org.eclipse.jdt.core.javaFormatter=org.eclipse.jdt.core.defaultJavaFormatter
--- /dev/null
+eclipse.preferences.version=1
+editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
+formatter_profile=_tmf-style
+formatter_settings_version=12
+org.eclipse.jdt.ui.exception.name=e
+org.eclipse.jdt.ui.gettersetter.use.is=true
+org.eclipse.jdt.ui.keywordthis=false
+org.eclipse.jdt.ui.overrideannotation=true
+sp_cleanup.add_default_serial_version_id=true
+sp_cleanup.add_generated_serial_version_id=false
+sp_cleanup.add_missing_annotations=false
+sp_cleanup.add_missing_deprecated_annotations=true
+sp_cleanup.add_missing_methods=false
+sp_cleanup.add_missing_nls_tags=false
+sp_cleanup.add_missing_override_annotations=true
+sp_cleanup.add_missing_override_annotations_interface_methods=true
+sp_cleanup.add_serial_version_id=false
+sp_cleanup.always_use_blocks=true
+sp_cleanup.always_use_parentheses_in_expressions=false
+sp_cleanup.always_use_this_for_non_static_field_access=false
+sp_cleanup.always_use_this_for_non_static_method_access=false
+sp_cleanup.convert_functional_interfaces=false
+sp_cleanup.convert_to_enhanced_for_loop=false
+sp_cleanup.correct_indentation=false
+sp_cleanup.format_source_code=false
+sp_cleanup.format_source_code_changes_only=false
+sp_cleanup.insert_inferred_type_arguments=false
+sp_cleanup.make_local_variable_final=false
+sp_cleanup.make_parameters_final=false
+sp_cleanup.make_private_fields_final=true
+sp_cleanup.make_type_abstract_if_missing_method=false
+sp_cleanup.make_variable_declarations_final=false
+sp_cleanup.never_use_blocks=false
+sp_cleanup.never_use_parentheses_in_expressions=true
+sp_cleanup.on_save_use_additional_actions=true
+sp_cleanup.organize_imports=true
+sp_cleanup.qualify_static_field_accesses_with_declaring_class=false
+sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
+sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
+sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
+sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
+sp_cleanup.remove_private_constructors=true
+sp_cleanup.remove_redundant_type_arguments=false
+sp_cleanup.remove_trailing_whitespaces=true
+sp_cleanup.remove_trailing_whitespaces_all=true
+sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
+sp_cleanup.remove_unnecessary_casts=false
+sp_cleanup.remove_unnecessary_nls_tags=false
+sp_cleanup.remove_unused_imports=false
+sp_cleanup.remove_unused_local_variables=false
+sp_cleanup.remove_unused_private_fields=true
+sp_cleanup.remove_unused_private_members=false
+sp_cleanup.remove_unused_private_methods=true
+sp_cleanup.remove_unused_private_types=true
+sp_cleanup.sort_members=false
+sp_cleanup.sort_members_all=false
+sp_cleanup.use_anonymous_class_creation=false
+sp_cleanup.use_blocks=true
+sp_cleanup.use_blocks_only_for_return_and_throw=false
+sp_cleanup.use_lambda=true
+sp_cleanup.use_parentheses_in_expressions=false
+sp_cleanup.use_this_for_non_static_field_access=false
+sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
+sp_cleanup.use_this_for_non_static_method_access=false
+sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true
--- /dev/null
+ANNOTATION_ELEMENT_TYPE_ADDED_METHOD_WITHOUT_DEFAULT_VALUE=Error
+ANNOTATION_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error
+ANNOTATION_ELEMENT_TYPE_REMOVED_FIELD=Error
+ANNOTATION_ELEMENT_TYPE_REMOVED_METHOD=Error
+ANNOTATION_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error
+API_COMPONENT_ELEMENT_TYPE_REMOVED_API_TYPE=Error
+API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_API_TYPE=Error
+API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_TYPE=Error
+API_COMPONENT_ELEMENT_TYPE_REMOVED_TYPE=Error
+API_USE_SCAN_FIELD_SEVERITY=Error
+API_USE_SCAN_METHOD_SEVERITY=Error
+API_USE_SCAN_TYPE_SEVERITY=Error
+CLASS_ELEMENT_TYPE_ADDED_METHOD=Error
+CLASS_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error
+CLASS_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error
+CLASS_ELEMENT_TYPE_CHANGED_CONTRACTED_SUPERINTERFACES_SET=Error
+CLASS_ELEMENT_TYPE_CHANGED_DECREASE_ACCESS=Error
+CLASS_ELEMENT_TYPE_CHANGED_NON_ABSTRACT_TO_ABSTRACT=Error
+CLASS_ELEMENT_TYPE_CHANGED_NON_FINAL_TO_FINAL=Error
+CLASS_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error
+CLASS_ELEMENT_TYPE_REMOVED_CONSTRUCTOR=Error
+CLASS_ELEMENT_TYPE_REMOVED_FIELD=Error
+CLASS_ELEMENT_TYPE_REMOVED_METHOD=Error
+CLASS_ELEMENT_TYPE_REMOVED_SUPERCLASS=Error
+CLASS_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error
+CLASS_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error
+CONSTRUCTOR_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error
+CONSTRUCTOR_ELEMENT_TYPE_CHANGED_DECREASE_ACCESS=Error
+CONSTRUCTOR_ELEMENT_TYPE_CHANGED_VARARGS_TO_ARRAY=Error
+CONSTRUCTOR_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error
+ENUM_ELEMENT_TYPE_CHANGED_CONTRACTED_SUPERINTERFACES_SET=Error
+ENUM_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error
+ENUM_ELEMENT_TYPE_REMOVED_ENUM_CONSTANT=Error
+ENUM_ELEMENT_TYPE_REMOVED_FIELD=Error
+ENUM_ELEMENT_TYPE_REMOVED_METHOD=Error
+ENUM_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error
+FIELD_ELEMENT_TYPE_ADDED_VALUE=Error
+FIELD_ELEMENT_TYPE_CHANGED_DECREASE_ACCESS=Error
+FIELD_ELEMENT_TYPE_CHANGED_FINAL_TO_NON_FINAL_STATIC_CONSTANT=Error
+FIELD_ELEMENT_TYPE_CHANGED_NON_FINAL_TO_FINAL=Error
+FIELD_ELEMENT_TYPE_CHANGED_NON_STATIC_TO_STATIC=Error
+FIELD_ELEMENT_TYPE_CHANGED_STATIC_TO_NON_STATIC=Error
+FIELD_ELEMENT_TYPE_CHANGED_TYPE=Error
+FIELD_ELEMENT_TYPE_CHANGED_VALUE=Error
+FIELD_ELEMENT_TYPE_REMOVED_TYPE_ARGUMENT=Error
+FIELD_ELEMENT_TYPE_REMOVED_VALUE=Error
+ILLEGAL_EXTEND=Warning
+ILLEGAL_IMPLEMENT=Warning
+ILLEGAL_INSTANTIATE=Warning
+ILLEGAL_OVERRIDE=Warning
+ILLEGAL_REFERENCE=Warning
+INTERFACE_ELEMENT_TYPE_ADDED_DEFAULT_METHOD=Ignore
+INTERFACE_ELEMENT_TYPE_ADDED_FIELD=Error
+INTERFACE_ELEMENT_TYPE_ADDED_METHOD=Error
+INTERFACE_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error
+INTERFACE_ELEMENT_TYPE_ADDED_SUPER_INTERFACE_WITH_METHODS=Error
+INTERFACE_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error
+INTERFACE_ELEMENT_TYPE_CHANGED_CONTRACTED_SUPERINTERFACES_SET=Error
+INTERFACE_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error
+INTERFACE_ELEMENT_TYPE_REMOVED_FIELD=Error
+INTERFACE_ELEMENT_TYPE_REMOVED_METHOD=Error
+INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error
+INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error
+INVALID_ANNOTATION=Warning
+INVALID_JAVADOC_TAG=Warning
+INVALID_REFERENCE_IN_SYSTEM_LIBRARIES=Warning
+LEAK_EXTEND=Warning
+LEAK_FIELD_DECL=Warning
+LEAK_IMPLEMENT=Warning
+LEAK_METHOD_PARAM=Warning
+LEAK_METHOD_RETURN_TYPE=Warning
+METHOD_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error
+METHOD_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error
+METHOD_ELEMENT_TYPE_CHANGED_DECREASE_ACCESS=Error
+METHOD_ELEMENT_TYPE_CHANGED_NON_ABSTRACT_TO_ABSTRACT=Error
+METHOD_ELEMENT_TYPE_CHANGED_NON_FINAL_TO_FINAL=Error
+METHOD_ELEMENT_TYPE_CHANGED_NON_STATIC_TO_STATIC=Error
+METHOD_ELEMENT_TYPE_CHANGED_STATIC_TO_NON_STATIC=Error
+METHOD_ELEMENT_TYPE_CHANGED_VARARGS_TO_ARRAY=Error
+METHOD_ELEMENT_TYPE_REMOVED_ANNOTATION_DEFAULT_VALUE=Error
+METHOD_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error
+MISSING_EE_DESCRIPTIONS=Ignore
+TYPE_PARAMETER_ELEMENT_TYPE_ADDED_CLASS_BOUND=Error
+TYPE_PARAMETER_ELEMENT_TYPE_ADDED_INTERFACE_BOUND=Error
+TYPE_PARAMETER_ELEMENT_TYPE_CHANGED_CLASS_BOUND=Error
+TYPE_PARAMETER_ELEMENT_TYPE_CHANGED_INTERFACE_BOUND=Error
+TYPE_PARAMETER_ELEMENT_TYPE_REMOVED_CLASS_BOUND=Error
+TYPE_PARAMETER_ELEMENT_TYPE_REMOVED_INTERFACE_BOUND=Error
+UNUSED_PROBLEM_FILTERS=Warning
+automatically_removed_unused_problem_filters=false
+changed_execution_env=Error
+eclipse.preferences.version=1
+incompatible_api_component_version=Error
+incompatible_api_component_version_include_major_without_breaking_change=Disabled
+incompatible_api_component_version_include_minor_without_api_change=Disabled
+incompatible_api_component_version_report_major_without_breaking_change=Warning
+incompatible_api_component_version_report_minor_without_api_change=Warning
+invalid_since_tag_version=Error
+malformed_since_tag=Error
+missing_since_tag=Error
+report_api_breakage_when_major_version_incremented=Disabled
+report_resolution_errors_api_component=Warning
--- /dev/null
+compilers.f.unresolved-features=1
+compilers.f.unresolved-plugins=1
+compilers.incompatible-environment=1
+compilers.p.build=1
+compilers.p.build.bin.includes=0
+compilers.p.build.encodings=2
+compilers.p.build.java.compiler=2
+compilers.p.build.java.compliance=1
+compilers.p.build.missing.output=2
+compilers.p.build.output.library=1
+compilers.p.build.source.library=0
+compilers.p.build.src.includes=0
+compilers.p.deprecated=1
+compilers.p.discouraged-class=1
+compilers.p.internal=1
+compilers.p.missing-packages=1
+compilers.p.missing-version-export-package=2
+compilers.p.missing-version-import-package=2
+compilers.p.missing-version-require-bundle=2
+compilers.p.no-required-att=0
+compilers.p.not-externalized-att=1
+compilers.p.unknown-attribute=1
+compilers.p.unknown-class=1
+compilers.p.unknown-element=1
+compilers.p.unknown-identifier=1
+compilers.p.unknown-resource=1
+compilers.p.unresolved-ex-points=0
+compilers.p.unresolved-import=0
+compilers.s.create-docs=false
+compilers.s.doc-folder=doc
+compilers.s.open-tags=1
+eclipse.preferences.version=1
--- /dev/null
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: %Bundle-Name
+Bundle-Vendor: %Bundle-Vendor
+Bundle-Version: 0.2.0.qualifier
+Bundle-Localization: plugin
+Bundle-SymbolicName: org.lttng.scope.tmf2.views.core;singleton:=true
+Bundle-Activator: org.lttng.scope.tmf2.views.core.activator.internal.Activator
+Bundle-ActivationPolicy: lazy
+Bundle-RequiredExecutionEnvironment: JavaSE-1.8
+Eclipse-ExtensibleAPI: true
+Require-Bundle: org.eclipse.core.runtime,
+ org.eclipse.core.resources,
+ org.lttng.scope.common.core,
+ ca.polymtl.dorsal.libdelorean,
+ org.eclipse.tracecompass.tmf.core
+Export-Package: org.lttng.scope.tmf2.views.core,
+ org.lttng.scope.tmf2.views.core.activator.internal;x-internal:=true,
+ org.lttng.scope.tmf2.views.core.config,
+ org.lttng.scope.tmf2.views.core.context,
+ org.lttng.scope.tmf2.views.core.timegraph.control,
+ org.lttng.scope.tmf2.views.core.timegraph.model.provider,
+ org.lttng.scope.tmf2.views.core.timegraph.model.provider.arrows,
+ org.lttng.scope.tmf2.views.core.timegraph.model.provider.drawnevents,
+ org.lttng.scope.tmf2.views.core.timegraph.model.provider.states,
+ org.lttng.scope.tmf2.views.core.timegraph.model.provider.statesystem,
+ org.lttng.scope.tmf2.views.core.timegraph.model.render,
+ org.lttng.scope.tmf2.views.core.timegraph.model.render.arrows,
+ org.lttng.scope.tmf2.views.core.timegraph.model.render.drawnevents,
+ org.lttng.scope.tmf2.views.core.timegraph.model.render.states,
+ org.lttng.scope.tmf2.views.core.timegraph.model.render.tree,
+ org.lttng.scope.tmf2.views.core.timegraph.view,
+ org.lttng.scope.tmf2.views.core.timegraph.view.json
+Import-Package: com.google.common.annotations;version="15.0.0",
+ com.google.common.base,
+ com.google.common.collect,
+ com.google.gson,
+ org.json
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"/>
+<title>About</title>
+</head>
+<body lang="EN-US">
+<h2>About This Content</h2>
+
+<p>June 5, 2006</p>
+<h3>License</h3>
+
+<p>The Eclipse Foundation makes available all content in this plug-in ("Content"). Unless otherwise
+indicated below, the Content is provided to you under the terms and conditions of the
+Eclipse Public License Version 1.0 ("EPL"). A copy of the EPL is available
+at <a href="http://www.eclipse.org/legal/epl-v10.html">http://www.eclipse.org/legal/epl-v10.html</a>.
+For purposes of the EPL, "Program" will mean the Content.</p>
+
+<p>If you did not receive this Content directly from the Eclipse Foundation, the Content is
+being redistributed by another party ("Redistributor") and different terms and conditions may
+apply to your use of any object code in the Content. Check the Redistributor's license that was
+provided with the Content. If no such license exists, contact the Redistributor. Unless otherwise
+indicated below, the terms and conditions of the EPL still apply to any source code in the Content
+and such source code may be obtained at <a href="http://www.eclipse.org/">http://www.eclipse.org</a>.</p>
+
+</body>
+</html>
\ No newline at end of file
--- /dev/null
+###############################################################################
+# Copyright (C) 2017 EfficiOS Inc.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Eclipse Public License v1.0
+# which accompanies this distribution, and is available at
+# http://www.eclipse.org/legal/epl-v10.html
+###############################################################################
+
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+ plugin.properties,\
+ about.html,\
+ .
+src.includes = about.html
+additional.bundles = org.eclipse.jdt.annotation
+jars.extra.classpath = platform:/plugin/org.eclipse.jdt.annotation
--- /dev/null
+###############################################################################
+# Copyright (C) 2017 EfficiOS Inc.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Eclipse Public License v1.0
+# which accompanies this distribution, and is available at
+# http://www.eclipse.org/legal/epl-v10.html
+###############################################################################
+
+Bundle-Vendor = LTTng Scope
+Bundle-Name = LTTng Scope TMF2 Core Plug-in
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.core;
+
+import javafx.beans.property.Property;
+import javafx.beans.value.ChangeListener;
+
+/**
+ * Simple class encapsulating a {@link ChangeListener} and its target property.
+ * It allows disabling and re-enabling the listener, by simply detaching it from
+ * its target, on demand. The calling class then only has one class to manage
+ * (this handler) instead of two.
+ *
+ * @author Alexandre Montplaisir
+ *
+ * @param <T>
+ * The type of property
+ */
+public final class ChangeListenerHandler<T> {
+
+ private final Property<T> fTarget;
+ private final ChangeListener<T> fListener;
+
+ /**
+ * Build a new listener handler. The listener will be added to the target
+ * (enabled).
+ *
+ * @param target
+ * The target property
+ * @param listener
+ * The listener
+ */
+ public ChangeListenerHandler(Property<T> target, ChangeListener<T> listener) {
+ fTarget = target;
+ fListener = listener;
+ enable();
+ }
+
+ /**
+ * Attach the listener to its property, re-enabling it.
+ */
+ public void enable() {
+ fTarget.addListener(fListener);
+ }
+
+ /**
+ * Detach the listener from its property, disabling it.
+ */
+ public void disable() {
+ fTarget.removeListener(fListener);
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.core;
+
+/**
+ * Uncommon math utility methods.
+ *
+ * @author Alexandre Montplaisir
+ */
+public final class MathUtils {
+
+ private MathUtils() {}
+
+ /**
+ * Find the multiple of 'multipleOf' that is greater but closest to
+ * 'number'. If 'number' is already a multiple of 'multipleOf', the same
+ * value will be returned.
+ *
+ * @param number
+ * The starting number
+ * @param multipleOf
+ * We want the returned value to be a multiple of this number
+ * @return The closest, greater multiple
+ */
+ public static long roundToClosestHigherMultiple(long number, long multipleOf) {
+ return (long) (Math.ceil((double) number / multipleOf) * multipleOf);
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.core;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javafx.beans.property.BooleanProperty;
+import javafx.beans.property.ReadOnlyBooleanProperty;
+import javafx.beans.property.SimpleBooleanProperty;
+import javafx.beans.value.ChangeListener;
+
+/**
+ * Utility class that serves as a wrapper around a single boolean flag that can
+ * be enabled/disabled. It counts the number of times {@link #disable()} is
+ * called, and only really re-enables the inner value when {@link #enable()} is
+ * called that many times.
+ *
+ * It is meant to be useful in multi-thread scenarios, where concurrent
+ * "critical sections" may want to disable something like a listener, and not
+ * have it really be re-enabled until all critical sections are finished. Thus
+ * it is thread-safe.
+ *
+ * The inner value is exposed through the {@link #enabledProperty()} method, which
+ * returns a {@link ReadOnlyBooleanProperty}. You can attach
+ * {@link ChangeListener}s to that property to get notified of inner value
+ * changes.
+ *
+ * It is "enabled" at creation time.
+ *
+ * @author Alexandre Montplaisir
+ */
+public class NestingBoolean {
+
+ private final AtomicInteger fDisabledCount = new AtomicInteger(0);
+ private final BooleanProperty fBoolean = new SimpleBooleanProperty(true);
+
+ /**
+ * Decrease the "disabled" count by 1. If it reaches (or already was at) 0
+ * then the value is truly enabled.
+ */
+ public synchronized void enable() {
+ /* Decrement the count but only if it is currently above 0 */
+ int ret = fDisabledCount.updateAndGet(value -> value > 0 ? value - 1 : 0);
+ if (ret == 0) {
+ fBoolean.set(true);
+ }
+ }
+
+ /**
+ * Increase the "disabled" count by 1. The inner value will necessarily be
+ * disabled after this call.
+ */
+ public synchronized void disable() {
+ fDisabledCount.incrementAndGet();
+ fBoolean.set(false);
+ }
+
+ /**
+ * Property representing the inner boolean value.
+ *
+ * @return The inner value
+ */
+ public ReadOnlyBooleanProperty enabledProperty() {
+ return fBoolean;
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.core;
+
+import java.util.Objects;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimeRange;
+import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimestamp;
+
+import com.google.common.base.MoreObjects;
+
+/**
+ * Simple class representing a time range, encapsulating two timestamps stored
+ * as long's.
+ *
+ * @author Alexandre Montplaisir
+ */
+public final class TimeRange {
+
+ private final long fStartTime;
+ private final long fEndTime;
+
+ private TimeRange(long startTime, long endTime) {
+ if (endTime < startTime) {
+ throw new IllegalArgumentException("End of time range earlier than the start:[" + startTime + ", " + endTime + "]"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ if (startTime < 0 || endTime < 0) {
+ throw new IllegalArgumentException("One of the bounds is negative:[" + startTime + ", " + endTime + "]"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ fStartTime = startTime;
+ fEndTime = endTime;
+ }
+
+ /**
+ * Factory method, creating a new time range from its start and end.
+ *
+ * @param startTime
+ * The start time of the range
+ * @param endTime
+ * The end time of the range. Should be equal or greater than the
+ * start time.
+ * @return The new time range
+ */
+ public static TimeRange of(long startTime, long endTime) {
+ // TODO Implement caching?
+ return new TimeRange(startTime, endTime);
+ }
+
+ /**
+ * Get the range's start time
+ *
+ * @return The start time
+ */
+ public long getStart() {
+ return fStartTime;
+ }
+
+ /**
+ * Get the range's end time
+ *
+ * @return The end time
+ */
+ public long getEnd() {
+ return fEndTime;
+ }
+
+ /**
+ * Get the duration, or "span", of this time range.
+ *
+ * @return The duration
+ */
+ public long getDuration() {
+ return (fEndTime - fStartTime);
+ }
+
+ /**
+ * Check if this time range contains, inclusively, the given timestamp.
+ *
+ * @param timestamp
+ * The timestamp to check
+ * @return True if the timestamp is contained in the range, false otherwise
+ */
+ public boolean contains(long timestamp) {
+ return (fStartTime <= timestamp && timestamp <= fEndTime);
+ }
+
+ /**
+ * Check if the bounds of this time range are the exact same one (single
+ * timestamp).
+ *
+ * @return If this range represents a single timestamp
+ */
+ public boolean isSingleTimestamp() {
+ return (fStartTime == fEndTime);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(fStartTime, fEndTime);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ TimeRange other = (TimeRange) obj;
+ return (fStartTime == other.fStartTime
+ && fEndTime == other.fEndTime);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("start", fStartTime) //$NON-NLS-1$
+ .add("end", fEndTime) //$NON-NLS-1$
+ .toString();
+ }
+
+ /**
+ * Convert this range into a {@link TmfTimeRange}.
+ *
+ * @return The equivalent TmfTimeRange
+ */
+ public TmfTimeRange toTmfTimeRange() {
+ return new TmfTimeRange(TmfTimestamp.fromNanos(fStartTime), TmfTimestamp.fromNanos(fEndTime));
+ }
+
+ /**
+ * Create a {@link TimeRange} from a {@link TmfTimeRange}.
+ *
+ * @param range
+ * The TmfTimeRange
+ * @return The TimeRange
+ */
+ public static TimeRange fromTmfTimeRange(TmfTimeRange range) {
+ return new TimeRange(range.getStartTime().toNanos(), range.getEndTime().toNanos());
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.core.activator.internal;
+
+import org.lttng.scope.common.core.ScopeCoreActivator;
+import org.lttng.scope.tmf2.views.core.context.ViewGroupContext;
+
+/**
+ * Plugin activator
+ *
+ * @noreference This class should not be accessed outside of this plugin.
+ */
+public class Activator extends ScopeCoreActivator {
+
+ /**
+ * Return the singleton instance of this activator.
+ *
+ * @return The singleton instance
+ */
+ public static Activator instance() {
+ return ScopeCoreActivator.getInstance(Activator.class);
+ }
+
+ @Override
+ protected void startActions() {
+ }
+
+ @Override
+ protected void stopActions() {
+ ViewGroupContext.cleanup();
+ }
+
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+@org.eclipse.jdt.annotation.NonNullByDefault
+package org.lttng.scope.tmf2.views.core.activator.internal;
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.core.config;
+
+import javafx.beans.property.SimpleObjectProperty;
+
+/**
+ * Configuration option, which is basically a JavaFX property with a default
+ * value.
+ *
+ * @author Alexandre Montplaisir
+ *
+ * @param <T>
+ * The type of property
+ */
+public class ConfigOption<T> extends SimpleObjectProperty<T> {
+
+ private final T fDefaultValue;
+
+ /**
+ * Constructor
+ *
+ * @param defaultValue
+ * The initial and default value
+ */
+ public ConfigOption(T defaultValue) {
+ super(defaultValue);
+ fDefaultValue = defaultValue;
+ }
+
+ /**
+ * Retrieve the default value of this option. This does not modify the
+ * current value.
+ *
+ * @return The default value
+ */
+ public T getDefaultValue() {
+ return fDefaultValue;
+ }
+
+ /**
+ * Reset the current value to its default.
+ */
+ public void resetToDefault() {
+ set(fDefaultValue);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.core.context;
+
+import org.eclipse.tracecompass.tmf.core.signal.TmfSelectionRangeUpdatedSignal;
+import org.eclipse.tracecompass.tmf.core.signal.TmfSignal;
+import org.eclipse.tracecompass.tmf.core.signal.TmfSignalHandler;
+import org.eclipse.tracecompass.tmf.core.signal.TmfSignalManager;
+import org.eclipse.tracecompass.tmf.core.signal.TmfSignalThrottler;
+import org.eclipse.tracecompass.tmf.core.signal.TmfTraceClosedSignal;
+import org.eclipse.tracecompass.tmf.core.signal.TmfTraceRangeUpdatedSignal;
+import org.eclipse.tracecompass.tmf.core.signal.TmfTraceSelectedSignal;
+import org.eclipse.tracecompass.tmf.core.signal.TmfWindowRangeUpdatedSignal;
+import org.eclipse.tracecompass.tmf.core.timestamp.ITmfTimestamp;
+import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimeRange;
+import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimestamp;
+import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
+import org.eclipse.tracecompass.tmf.core.trace.TmfTraceManager;
+import org.lttng.scope.tmf2.views.core.TimeRange;
+
+/**
+ * Bridge between a {@link ViewGroupContext} and the {@link TmfSignalManager}.
+ * It sends equivalent "signals" back-and-forth between the two APIs.
+ *
+ * Note: Needs to be public because of the way TmfSignalManager works.
+ *
+ * @author Alexandre Montplaisir
+ */
+public class SignalBridge {
+
+ private static final int SIGNAL_DELAY_MS = 200;
+
+ private final TmfSignalThrottler fVisibleRangeSignalThrottler = new TmfSignalThrottler(null, SIGNAL_DELAY_MS);
+
+ private final ViewGroupContext fViewContext;
+
+ /**
+ * Constructor
+ *
+ * @param viewContext
+ * The view context this bridge will connect to
+ */
+ public SignalBridge(ViewGroupContext viewContext) {
+ TmfSignalManager.register(this);
+ fViewContext = viewContext;
+
+ viewContext.currentTraceProperty().addListener((obs, oldTrace, newTrace) -> {
+ if (newTrace == null) {
+ return;
+ }
+ TmfSignal newTraceSignal = new TmfTraceSelectedSignal(SignalBridge.this, newTrace);
+ TmfSignalManager.dispatchSignal(newTraceSignal);
+
+ viewContext.currentTraceFullRangeProperty().addListener((observable, oldRange, newRange) -> {
+ TmfTimeRange tmfTimeRange = newRange.toTmfTimeRange();
+ TmfSignal signal = new TmfTraceRangeUpdatedSignal(SignalBridge.this, viewContext.getCurrentTrace(), tmfTimeRange);
+ TmfSignalManager.dispatchSignal(signal);
+ });
+
+ viewContext.currentVisibleTimeRangeProperty().addListener((observable, oldRange, newRange) -> {
+ TmfTimeRange tmfTimeRange = newRange.toTmfTimeRange();
+ TmfSignal signal = new TmfWindowRangeUpdatedSignal(SignalBridge.this, tmfTimeRange);
+ fVisibleRangeSignalThrottler.queue(signal);
+ });
+
+ viewContext.currentSelectionTimeRangeProperty().addListener((observable, oldRange, newRange) -> {
+ TmfSignal signal;
+ if (newRange.isSingleTimestamp()) {
+ ITmfTimestamp ts = TmfTimestamp.fromNanos(newRange.getStart());
+ signal = new TmfSelectionRangeUpdatedSignal(SignalBridge.this, ts);
+ } else {
+ ITmfTimestamp startTs = TmfTimestamp.fromNanos(newRange.getStart());
+ ITmfTimestamp endTs = TmfTimestamp.fromNanos(newRange.getEnd());
+ signal = new TmfSelectionRangeUpdatedSignal(SignalBridge.this, startTs, endTs);
+ }
+ TmfSignalManager.dispatchSignal(signal);
+ });
+
+ });
+ }
+
+ /**
+ * Dispose of this bridge, deregistering it from the signal manager.
+ */
+ public void dispose() {
+ TmfSignalManager.deregister(this);
+ }
+
+ // ------------------------------------------------------------------------
+ // Signal handlers
+ // ------------------------------------------------------------------------
+
+ /**
+ * Handler for the trace selected signal
+ *
+ * @param signal
+ * Received signal
+ */
+ @TmfSignalHandler
+ public void traceSelected(final TmfTraceSelectedSignal signal) {
+ if (signal.getSource() == this) {
+ return;
+ }
+ ITmfTrace trace = signal.getTrace();
+ fViewContext.setCurrentTrace(trace);
+ }
+
+ /**
+ * Handler for the trace closed signal
+ *
+ * @param signal
+ * Received signal
+ */
+ @TmfSignalHandler
+ public void traceClosed(final TmfTraceClosedSignal signal) {
+ if (signal.getSource() == this) {
+ return;
+ }
+ if (TmfTraceManager.getInstance().getActiveTrace() == null) {
+ fViewContext.setCurrentTrace(null);
+ }
+ }
+
+ /**
+ * Handler for the trace range updated signal
+ *
+ * @param signal
+ * Received signal
+ */
+ @TmfSignalHandler
+ public void traceRangeUpdated(TmfTraceRangeUpdatedSignal signal) {
+ if (signal.getSource() == this) {
+ return;
+ }
+ /*
+ * This signal is a disaster, it's very inconsistent, has no guarantee
+ * of even showing up and sometimes gives values outside of the trace's
+ * own range. Best to ignore its contents completely.
+ */
+
+ ITmfTrace trace = fViewContext.getCurrentTrace();
+ if (trace == null) {
+ return;
+ }
+ long traceStart = trace.getStartTime().toNanos();
+ long traceEnd = trace.getEndTime().toNanos();
+ fViewContext.setCurrentTraceFullRange(TimeRange.of(traceStart, traceEnd));
+ }
+
+ /**
+ * Handler for the selection range updated signal
+ *
+ * @param signal
+ * Received signal
+ */
+ @TmfSignalHandler
+ public void selectionRangeUpdated(final TmfSelectionRangeUpdatedSignal signal) {
+ if (signal.getSource() == this) {
+ return;
+ }
+ long rangeStart = signal.getBeginTime().toNanos();
+ long rangeEnd = signal.getEndTime().toNanos();
+
+ /* Sometimes the range is weird... */
+ if (rangeStart == Long.MAX_VALUE || rangeEnd == Long.MAX_VALUE) {
+ return;
+ }
+
+ /*
+ * This signal's end can be before its start time, against all logic.
+ */
+ TimeRange range;
+ if (rangeStart > rangeEnd) {
+ range = TimeRange.of(rangeEnd, rangeStart);
+ } else {
+ range = TimeRange.of(rangeStart, rangeEnd);
+ }
+
+ ITmfTrace trace = fViewContext.getCurrentTrace();
+ if (trace == null) {
+ return;
+ }
+ fViewContext.setCurrentSelectionTimeRange(range);
+ }
+
+ /**
+ * Handler for the window range updated signal
+ *
+ * @param signal
+ * Received signal
+ */
+ @TmfSignalHandler
+ public void windowRangeUpdated(final TmfWindowRangeUpdatedSignal signal) {
+ if (signal.getSource() == this) {
+ return;
+ }
+ TmfTimeRange windowRange = signal.getCurrentRange();
+ ITmfTrace trace = fViewContext.getCurrentTrace();
+ if (windowRange == null || trace == null) {
+ return;
+ }
+ fViewContext.setCurrentVisibleTimeRange(TimeRange.fromTmfTimeRange(windowRange));
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.core.context;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
+import org.lttng.scope.tmf2.views.core.TimeRange;
+
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.ReadOnlyObjectProperty;
+import javafx.beans.property.SimpleObjectProperty;
+
+/**
+ * A common context for a group of views. Information is stored as properties,
+ * and views can add listeners to get notified of value changes.
+ *
+ * @author Alexandre Montplaisir
+ */
+public class ViewGroupContext {
+
+ /** Value representing uninitialized timestamps */
+ public static final TimeRange UNINITIALIZED_RANGE = TimeRange.of(0, 0);
+
+ private final SignalBridge fSignalBridge;
+
+ private final ObjectProperty<@Nullable ITmfTrace> fCurrentTrace = new SimpleObjectProperty<>(null);
+
+ private ObjectProperty<TimeRange> fCurrentTraceFullRange = new SimpleObjectProperty<>(UNINITIALIZED_RANGE);
+ private ObjectProperty<TimeRange> fCurrentVisibleTimeRange = new SimpleObjectProperty<>(UNINITIALIZED_RANGE);
+ private ObjectProperty<TimeRange> fCurrentSelectionRange = new SimpleObjectProperty<>(UNINITIALIZED_RANGE);
+
+ /**
+ * The context is a singleton for now, but the framework could be extended
+ * to support several contexts (one per "view group") at the same time.
+ */
+ private static @Nullable ViewGroupContext INSTANCE;
+
+ private ViewGroupContext() {
+ fSignalBridge = new SignalBridge(this);
+ }
+
+ /**
+ * For now, there is only a single view context for the framework. This
+ * method returns this singleton instance.
+ *
+ * @return The view context
+ */
+ public static ViewGroupContext getCurrent() {
+ ViewGroupContext ctx = INSTANCE;
+ if (ctx != null) {
+ return ctx;
+ }
+ ctx = new ViewGroupContext();
+ INSTANCE = ctx;
+ return ctx;
+ }
+
+ /**
+ * Cleanup all view group contexts.
+ */
+ public static void cleanup() {
+ ViewGroupContext ctx = INSTANCE;
+ if (ctx != null) {
+ ctx.fSignalBridge.dispose();
+ }
+ }
+
+ /**
+ * Set the current trace being displayed by this view context.
+ *
+ * @param trace
+ * The trace, can be null to indicate no trace
+ */
+ public void setCurrentTrace(@Nullable ITmfTrace trace) {
+ /* On trace change, adjust the other properties accordingly. */
+ if (trace == null) {
+ fCurrentTraceFullRange = new SimpleObjectProperty<>(UNINITIALIZED_RANGE);
+ fCurrentVisibleTimeRange = new SimpleObjectProperty<>(UNINITIALIZED_RANGE);
+ fCurrentSelectionRange = new SimpleObjectProperty<>(UNINITIALIZED_RANGE);
+
+ } else {
+ long traceStart = trace.getStartTime().toNanos();
+ long traceEnd = trace.getEndTime().toNanos();
+ long visibleRangeEnd = Math.min(traceStart + trace.getInitialRangeOffset().toNanos(), traceEnd);
+
+ fCurrentTraceFullRange = new SimpleObjectProperty<>((TimeRange.of(traceStart, traceEnd)));
+ fCurrentVisibleTimeRange = new SimpleObjectProperty<>((TimeRange.of(traceStart, visibleRangeEnd)));
+ fCurrentSelectionRange = new SimpleObjectProperty<>((TimeRange.of(traceStart, traceStart)));
+ }
+
+ fCurrentTrace.set(trace);
+ }
+
+ /**
+ * Retrieve the current trace displayed by this view context.
+ *
+ * @return The context's current trace. Can be null to indicate no trace.
+ */
+ public @Nullable ITmfTrace getCurrentTrace() {
+ return fCurrentTrace.get();
+ }
+
+ /**
+ * The current trace property.
+ *
+ * Make sure you use {@link #setCurrentTrace} to modify the current trace.
+ *
+ * @return The current trace property.
+ */
+ public ReadOnlyObjectProperty<@Nullable ITmfTrace> currentTraceProperty() {
+ return fCurrentTrace;
+ }
+
+ /**
+ * Set a new full range for the current trace
+ *
+ * @param range
+ * The new full range of the trace
+ */
+ public void setCurrentTraceFullRange(TimeRange range) {
+ fCurrentTraceFullRange.set(range);
+ }
+
+ /**
+ * Get the full range of the current trace.
+ *
+ * @return The full range of the trace
+ */
+ public TimeRange getCurrentTraceFullRange() {
+ return fCurrentTraceFullRange.get();
+ }
+
+ /**
+ * The property representing the full range of the current trace.
+ *
+ * TODO This property should move to the trace object itself.
+ *
+ * @return The full range property
+ */
+ public ObjectProperty<TimeRange> currentTraceFullRangeProperty() {
+ return fCurrentTraceFullRange;
+ }
+
+ /**
+ * Set the current visible time range of this view context.
+ *
+ * @param range
+ * The new visible time range
+ */
+ public void setCurrentVisibleTimeRange(TimeRange range) {
+ fCurrentVisibleTimeRange.set(range);
+ }
+
+ /**
+ * Retrieve the current visible time range of the view context.
+ *
+ * @return The current visible time range
+ */
+ public TimeRange getCurrentVisibleTimeRange() {
+ return fCurrentVisibleTimeRange.get();
+ }
+
+ /**
+ * The visible time range property. This indicates the time range bounded by
+ * the views of this context.
+ *
+ * @return The visible time range property
+ */
+ public ObjectProperty<TimeRange> currentVisibleTimeRangeProperty() {
+ return fCurrentVisibleTimeRange;
+ }
+
+ /**
+ * Set the current time range selection.
+ *
+ * @param range
+ * The new selection range
+ */
+ public void setCurrentSelectionTimeRange(TimeRange range) {
+ fCurrentSelectionRange.set(range);
+ }
+
+ /**
+ * Retrieve the current time range selection
+ *
+ * @return The current selection range
+ */
+ public TimeRange getCurrentSelectionTimeRange() {
+ return fCurrentSelectionRange.get();
+ }
+
+ /**
+ * The property representing the current time range selection. This is the
+ * "highlighted" part of the trace, which can be selected by the mouse or
+ * other means, and on which action can act.
+ *
+ * @return The time range selection property
+ */
+ public ObjectProperty<TimeRange> currentSelectionTimeRangeProperty() {
+ return fCurrentSelectionRange;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+@org.eclipse.jdt.annotation.NonNullByDefault
+package org.lttng.scope.tmf2.views.core.context;
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+@org.eclipse.jdt.annotation.NonNullByDefault
+package org.lttng.scope.tmf2.views.core;
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *******************************************************************************/
+
+package org.lttng.scope.tmf2.views.core.timegraph.control;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
+import org.lttng.scope.tmf2.views.core.TimeRange;
+import org.lttng.scope.tmf2.views.core.context.ViewGroupContext;
+import org.lttng.scope.tmf2.views.core.timegraph.model.provider.ITimeGraphModelProvider;
+import org.lttng.scope.tmf2.views.core.timegraph.view.TimeGraphModelView;
+
+import javafx.beans.value.ChangeListener;
+
+/**
+ * Control part of the timegraph MVC mechanism. It links a
+ * {@link TimeGraphModelView} to a {@link ITimeGraphModelProvider}.
+ *
+ * @author Alexandre Montplaisir
+ */
+public final class TimeGraphModelControl {
+
+ private final ChangeListener<TimeRange> fVisibleRangeChangeListener = (obs, oldRange, newRange) -> seekVisibleRange(newRange);
+
+ private final ViewGroupContext fViewContext;
+ private final ITimeGraphModelProvider fRenderProvider;
+
+ private @Nullable TimeGraphModelView fView = null;
+
+ /**
+ * Constructor.
+ *
+ * The control links a model provider, and a view. But the view also needs a
+ * back-reference to the control. The suggested pattern is to do:
+ *
+ * <pre>
+ * ITimeGraphModelProvider provider = ...
+ * TimeGraphModelControl control = new TimeGraphModelControl(viewContext, provider);
+ * TimeGraphModelView view = new TimeGraphModelView(control);
+ * control.attachView(view);
+ * </pre>
+ *
+ * @param viewContext
+ * The view context to which this timegraph belongs
+ * @param provider
+ * The model provider that goes with this control
+ */
+ public TimeGraphModelControl(ViewGroupContext viewContext, ITimeGraphModelProvider provider) {
+ fViewContext = viewContext;
+ fRenderProvider = provider;
+
+ attachListeners(viewContext);
+ }
+
+ /**
+ * Attach a view to this control
+ *
+ * @param view
+ * The view to attach
+ */
+ public void attachView(TimeGraphModelView view) {
+ fView = view;
+
+ /*
+ * Initially populate the view with the context of the current trace.
+ */
+ ITmfTrace trace = getViewContext().getCurrentTrace();
+ initializeForTrace(trace);
+ }
+
+ @Nullable TimeGraphModelView getView() {
+ return fView;
+ }
+
+ /**
+ * Dispose this control and its components.
+ */
+ public void dispose() {
+ if (fView != null) {
+ fView.dispose();
+ }
+ }
+
+ private void attachListeners(ViewGroupContext viewContext) {
+ viewContext.currentTraceProperty().addListener((observable, oldTrace, newTrace) -> {
+ initializeForTrace(newTrace);
+
+ viewContext.currentSelectionTimeRangeProperty().addListener((obs, oldRange, newRange) -> {
+ drawSelection(newRange);
+ });
+
+ viewContext.currentVisibleTimeRangeProperty().addListener(fVisibleRangeChangeListener);
+ });
+ }
+
+ // ------------------------------------------------------------------------
+ // Accessors
+ // ------------------------------------------------------------------------
+
+ /**
+ * Get the view context to which this control belongs.
+ *
+ * @return The view context
+ */
+ public ViewGroupContext getViewContext() {
+ return fViewContext;
+ }
+
+ /**
+ * Get the model provider of this control
+ *
+ * @return The model provider
+ */
+ public ITimeGraphModelProvider getModelRenderProvider() {
+ return fRenderProvider;
+ }
+
+ // ------------------------------------------------------------------------
+ // Control -> View operations
+ // ------------------------------------------------------------------------
+
+ /**
+ * Initialize this timegraph for a new trace.
+ *
+ * @param trace
+ * The trace to initialize in the view. If it is null it indcates
+ * 'no trace'.
+ */
+ public synchronized void initializeForTrace(@Nullable ITmfTrace trace) {
+ fRenderProvider.setTrace(trace);
+
+ TimeGraphModelView view = fView;
+ if (view == null) {
+ return;
+ }
+ view.clear();
+
+ if (trace == null) {
+ /* View will remain cleared, good */
+ return;
+ }
+
+ TimeRange currentVisibleRange = fViewContext.getCurrentVisibleTimeRange();
+ checkWindowTimeRange(currentVisibleRange);
+ view.seekVisibleRange(currentVisibleRange);
+ }
+
+ /**
+ * Repaint, without seeking anywhere else, the current displayed area of the
+ * view.
+ *
+ * This can be called whenever some settings like filters etc. have changed,
+ * so that a repaint will show updated information.
+ */
+ public void repaintCurrentArea() {
+ ITmfTrace trace = fViewContext.getCurrentTrace();
+ TimeRange currentRange = fViewContext.getCurrentVisibleTimeRange();
+ if (trace == null || currentRange == ViewGroupContext.UNINITIALIZED_RANGE) {
+ return;
+ }
+
+ TimeGraphModelView view = fView;
+ if (view != null) {
+ view.clear();
+ view.seekVisibleRange(currentRange);
+ }
+ }
+
+ void seekVisibleRange(TimeRange newRange) {
+ checkWindowTimeRange(newRange);
+ TimeGraphModelView view = fView;
+ if (view != null) {
+ view.seekVisibleRange(newRange);
+ }
+ }
+
+ void drawSelection(TimeRange selectionRange) {
+ checkWindowTimeRange(selectionRange);
+ TimeGraphModelView view = fView;
+ if (view != null) {
+ view.drawSelection(selectionRange);
+ }
+ }
+
+
+
+ // ------------------------------------------------------------------------
+ // View -> Control operations (Control external API)
+ // ------------------------------------------------------------------------
+
+ /**
+ * Change the current time range selection.
+ *
+ * Called by the view to indicate that the user has input a new time range
+ * selection from the view. The control will relay this to the rest of the
+ * framework.
+ *
+ * @param newSelectionRange
+ * The new time range selection.
+ */
+ public void updateTimeRangeSelection(TimeRange newSelectionRange) {
+ fViewContext.setCurrentSelectionTimeRange(newSelectionRange);
+ }
+
+ /**
+ * Change the current visible time range.
+ *
+ * Called by the view whenever the user selects a new visible time range,
+ * for example by scrolling left or right.
+ *
+ * @param newVisibleRange
+ * The new visible time range
+ * @param echo
+ * This flag indicates if the view wants to receive the new time
+ * range notification back to itself (via
+ * {@link #seekVisibleRange}.
+ */
+ public void updateVisibleTimeRange(TimeRange newVisibleRange, boolean echo) {
+ checkTimeRange(newVisibleRange);
+
+ /*
+ * If 'echo' is 'off', we will avoid triggering our change listener on
+ * this modification by detaching then re-attaching it afterwards.
+ */
+ if (echo) {
+ fViewContext.setCurrentVisibleTimeRange(newVisibleRange);
+ } else {
+ fViewContext.currentVisibleTimeRangeProperty().removeListener(fVisibleRangeChangeListener);
+ fViewContext.setCurrentVisibleTimeRange(newVisibleRange);
+ fViewContext.currentVisibleTimeRangeProperty().addListener(fVisibleRangeChangeListener);
+ }
+ }
+
+ // ------------------------------------------------------------------------
+ // Utils
+ // ------------------------------------------------------------------------
+
+ private static void checkTimeRange(TimeRange range) {
+ if (range.getStart() == Long.MAX_VALUE) {
+ throw new IllegalArgumentException("You are trying to make me believe the range starts at " + //$NON-NLS-1$
+ range.getStart() + ". I do not believe you."); //$NON-NLS-1$
+ }
+ if (range.getEnd() == Long.MAX_VALUE) {
+ throw new IllegalArgumentException("You are trying to make me believe the range ends at " + //$NON-NLS-1$
+ range.getEnd() + ". I do not believe you."); //$NON-NLS-1$
+ }
+ }
+
+ private void checkWindowTimeRange(TimeRange windowRange) {
+ checkTimeRange(windowRange);
+ TimeRange fullRange = fViewContext.getCurrentTraceFullRange();
+
+ if (windowRange.getStart() < fullRange.getStart()) {
+ throw new IllegalArgumentException("Requested window start time: " + windowRange.getStart() + //$NON-NLS-1$
+ " is smaller than trace start time " + fullRange.getStart()); //$NON-NLS-1$
+ }
+ if (windowRange.getEnd() > fullRange.getEnd()) {
+ throw new IllegalArgumentException("Requested window end time: " + windowRange.getEnd() + //$NON-NLS-1$
+ " is greater than trace end time " + fullRange.getEnd()); //$NON-NLS-1$
+ }
+ }
+
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc. and others
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *******************************************************************************/
+
+@org.eclipse.jdt.annotation.NonNullByDefault
+package org.lttng.scope.tmf2.views.core.timegraph.control;
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *******************************************************************************/
+
+package org.lttng.scope.tmf2.views.core.timegraph.model.provider;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
+import org.lttng.scope.tmf2.views.core.timegraph.model.provider.arrows.ITimeGraphModelArrowProvider;
+import org.lttng.scope.tmf2.views.core.timegraph.model.provider.states.ITimeGraphModelStateProvider;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.tree.TimeGraphTreeRender;
+
+import javafx.beans.property.ObjectProperty;
+
+/**
+ * Base interface for time graph model providers.
+ *
+ * This object is responsible for the generation of the "tree" part of the
+ * timegraph, and other generic options like sorting and filtering modes.
+ *
+ * It also encapsulates one {@link ITimeGraphModelStateProvider} (which is
+ * responsible of providing state intervals), and zero or more
+ * {@link ITimeGraphModelArrowProvider} (which provide model-defined arrow
+ * series).
+ *
+ * @author Alexandre Montplaisir
+ */
+public interface ITimeGraphModelProvider {
+
+ // ------------------------------------------------------------------------
+ // Configuration option classes
+ // ------------------------------------------------------------------------
+
+ /**
+ * Class representing one sorting mode. A sorting mode is like a comparator
+ * to sort tree elements. Only one can be active at any time.
+ *
+ * The exact behavior of the sorting mode is defined by the model provider
+ * itself.
+ */
+ class SortingMode {
+
+ private final String fName;
+
+ public SortingMode(String name) {
+ fName = name;
+ }
+
+ public String getName() {
+ return fName;
+ }
+ }
+
+ /**
+ * Class representing a filter mode. A filter mode is like a filter applied
+ * on the list tree elements. Zero or more can be active at the same time.
+ *
+ * The exact behavior of the filter mode is defined by the model provider
+ * itself.
+ */
+ class FilterMode {
+
+ private final String fName;
+
+ public FilterMode(String name) {
+ fName = name;
+ }
+
+ public String getName() {
+ return fName;
+ }
+ }
+
+ // ------------------------------------------------------------------------
+ // General methods
+ // ------------------------------------------------------------------------
+
+ /**
+ * Get the name of this model provider. This can be used for example to name
+ * a corresponding view in the UI.
+ *
+ * @return The model provider's name
+ */
+ String getName();
+
+ /**
+ * Set the trace for which this model provider fetches its information.
+ *
+ * @param trace
+ * The source trace
+ */
+ void setTrace(@Nullable ITmfTrace trace);
+
+
+ /**
+ * Get the trace for which this model provider fetches its information.
+ *
+ * @return The current trace
+ */
+ @Nullable ITmfTrace getTrace();
+
+ /**
+ * The property representing the target trace of this model provider.
+ *
+ * @return The trace property
+ */
+ ObjectProperty<@Nullable ITmfTrace> traceProperty();
+
+ // ------------------------------------------------------------------------
+ // Render providers
+ // ------------------------------------------------------------------------
+
+ /**
+ * Get a tree render corresponding to the current configuration settings.
+ *
+ * @return A tree render
+ */
+ TimeGraphTreeRender getTreeRender();
+
+ /**
+ * Get the state provider supplied by this model provider.
+ *
+ * @return The state provider
+ */
+ ITimeGraphModelStateProvider getStateProvider();
+
+ /**
+ * Get the arrow providers supplied by this model provider.
+ *
+ * @return The arrow providers. May be empty but should not be null.
+ */
+ Collection<ITimeGraphModelArrowProvider> getArrowProviders();
+
+ // ------------------------------------------------------------------------
+ // Sorting modes
+ // ------------------------------------------------------------------------
+
+ /**
+ * Get a list of all the available sorting modes for this provider.
+ *
+ * @return The sorting modes
+ */
+ List<SortingMode> getSortingModes();
+
+ /**
+ * Get the current sorting mode. There should always be one and only one.
+ *
+ * @return The current sorting mode
+ */
+ SortingMode getCurrentSortingMode();
+
+ /**
+ * Change the configured sorting mode to another one.
+ *
+ * @param index
+ * The index of the corresponding mode in the list returned by
+ * {@link #getSortingModes()}.
+ */
+ void setCurrentSortingMode(int index);
+
+ // ------------------------------------------------------------------------
+ // Filter modes
+ // ------------------------------------------------------------------------
+
+ /**
+ * Get a list of all the available filter modes for this provider.
+ *
+ * @return The list of available filter modes. It may be empty but should
+ * not be null.
+ */
+ List<FilterMode> getFilterModes();
+
+ /**
+ * Enable the specified filter mode.
+ *
+ * @param index
+ * The index of the filter mode in the list returned by
+ * {@link #getFilterModes()}.
+ */
+ void enableFilterMode(int index);
+
+ /**
+ * Disable the specified filter mode.
+ *
+ * @param index
+ * The index of the filter mode in the list returned by
+ * {@link #getFilterModes()}.
+ */
+ void disableFilterMode(int index);
+
+ /**
+ * Get the currently active filter modes.
+ *
+ * @return The active filter modes. There might be 0 or more.
+ */
+ Set<FilterMode> getActiveFilterModes();
+
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.core.timegraph.model.provider;
+
+import java.util.function.Supplier;
+
+/**
+ * Factory for {@link ITimeGraphModelProvider} objects.
+ *
+ * Used to register possible time graphs to the framework using the
+ * {@link TimeGraphModelProviderManager}.
+ *
+ * @author Alexandre Montplaisir
+ */
+@FunctionalInterface
+public interface ITimeGraphModelProviderFactory extends Supplier<ITimeGraphModelProvider> {
+
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *******************************************************************************/
+
+package org.lttng.scope.tmf2.views.core.timegraph.model.provider;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * Package message bundle
+ *
+ * @noreference Messages class
+ */
+@NonNullByDefault({})
+@SuppressWarnings("javadoc")
+public class Messages extends NLS {
+
+ private static final String BUNDLE_NAME = Messages.class.getPackage().getName() + ".messages"; //$NON-NLS-1$
+
+ public static String DefaultSortingModeName;
+
+ static {
+ NLS.initializeMessages(BUNDLE_NAME, Messages.class);
+ }
+
+ private Messages() {}
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *******************************************************************************/
+
+package org.lttng.scope.tmf2.views.core.timegraph.model.provider;
+
+import static org.lttng.scope.common.core.NonNullUtils.nullToEmptyString;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
+import org.lttng.scope.tmf2.views.core.timegraph.model.provider.arrows.ITimeGraphModelArrowProvider;
+import org.lttng.scope.tmf2.views.core.timegraph.model.provider.states.ITimeGraphModelStateProvider;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.tree.TimeGraphTreeRender;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.SimpleObjectProperty;
+
+/**
+ * Base implementation of {@link ITimeGraphModelProvider}.
+ *
+ * @author Alexandre Montplaisir
+ */
+public abstract class TimeGraphModelProvider implements ITimeGraphModelProvider {
+
+ /**
+ * A "default" sorting mode, for use when only one is needed.
+ */
+ protected static final SortingMode DEFAULT_SORTING_MODE = new SortingMode(nullToEmptyString(Messages.DefaultSortingModeName));
+
+ private final String fName;
+ private final List<SortingMode> fSortingModes;
+ private final List<FilterMode> fFilterModes;
+
+ private final ITimeGraphModelStateProvider fStateProvider;
+ private final List<ITimeGraphModelArrowProvider> fArrowProviders;
+
+ private final Set<FilterMode> fActiveFilterModes = new HashSet<>();
+ private SortingMode fCurrentSortingMode;
+
+ private final ObjectProperty<@Nullable ITmfTrace> fTraceProperty = new SimpleObjectProperty<>();
+
+ /**
+ * Constructor
+ *
+ * @param name
+ * The name of this provider
+ * @param sortingModes
+ * The available sorting modes
+ * @param filterModes
+ * The available filter modes
+ * @param stateProvider
+ * The state provider part of this model provider
+ * @param arrowProviders
+ * The arrow provider(s) supplied by this model provider
+ */
+ public TimeGraphModelProvider(String name,
+ @Nullable List<SortingMode> sortingModes,
+ @Nullable List<FilterMode> filterModes,
+ ITimeGraphModelStateProvider stateProvider,
+ @Nullable List<ITimeGraphModelArrowProvider> arrowProviders) {
+ fName = name;
+
+ fStateProvider = stateProvider;
+ stateProvider.traceProperty().bind(fTraceProperty);
+
+ if (sortingModes == null || sortingModes.isEmpty()) {
+ fSortingModes = ImmutableList.of(DEFAULT_SORTING_MODE);
+ } else {
+ fSortingModes = ImmutableList.copyOf(sortingModes);
+
+ }
+ fCurrentSortingMode = fSortingModes.get(0);
+
+ if (filterModes == null || filterModes.isEmpty()) {
+ fFilterModes = ImmutableList.of();
+ } else {
+ fFilterModes = ImmutableList.copyOf(filterModes);
+ }
+
+ if (arrowProviders == null || arrowProviders.isEmpty()) {
+ fArrowProviders = ImmutableList.of();
+ } else {
+ fArrowProviders = ImmutableList.copyOf(arrowProviders);
+ }
+ fArrowProviders.forEach(ap -> ap.traceProperty().bind(fTraceProperty));
+ }
+
+ @Override
+ public final String getName() {
+ return fName;
+ }
+
+ @Override
+ public final void setTrace(@Nullable ITmfTrace trace) {
+ fTraceProperty.set(trace);
+ }
+
+ @Override
+ public final @Nullable ITmfTrace getTrace() {
+ return fTraceProperty.get();
+ }
+
+ @Override
+ public final ObjectProperty<@Nullable ITmfTrace> traceProperty() {
+ return fTraceProperty;
+ }
+
+ @Override
+ public final ITimeGraphModelStateProvider getStateProvider() {
+ return fStateProvider;
+ }
+
+ @Override
+ public final List<ITimeGraphModelArrowProvider> getArrowProviders() {
+ return fArrowProviders;
+ }
+
+ // ------------------------------------------------------------------------
+ // Render generation methods. Implementation left to subclasses.
+ // ------------------------------------------------------------------------
+
+ @Override
+ public abstract TimeGraphTreeRender getTreeRender();
+
+ // ------------------------------------------------------------------------
+ // Sorting modes
+ // ------------------------------------------------------------------------
+
+ @Override
+ public final List<SortingMode> getSortingModes() {
+ return fSortingModes;
+ }
+
+ @Override
+ public final SortingMode getCurrentSortingMode() {
+ return fCurrentSortingMode;
+ }
+
+ @Override
+ public final void setCurrentSortingMode(int index) {
+ fCurrentSortingMode = fSortingModes.get(index);
+ }
+
+ // ------------------------------------------------------------------------
+ // Filter modes
+ // ------------------------------------------------------------------------
+
+ @Override
+ public final List<FilterMode> getFilterModes() {
+ return fFilterModes;
+ }
+
+ @Override
+ public final void enableFilterMode(int index) {
+ fActiveFilterModes.add(fFilterModes.get(index));
+ }
+
+ @Override
+ public final void disableFilterMode(int index) {
+ fActiveFilterModes.remove(fFilterModes.get(index));
+ }
+
+ @Override
+ public final Set<FilterMode> getActiveFilterModes() {
+ return ImmutableSet.copyOf(fActiveFilterModes);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.core.timegraph.model.provider;
+
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+/**
+ * Manager for the time graph model providers.
+ *
+ * Components can register their possible time graphs, and views etc. may query
+ * and instantiate them.
+ *
+ * @author Alexandre Montplaisir
+ */
+public final class TimeGraphModelProviderManager {
+
+ /**
+ * Interface for classes displaying Time Graphs. Implement this interface and
+ * register your class using {@link #registerOutput} to receive notifications
+ * about current and newly-registered time graph providers.
+ */
+ public static interface TimeGraphOutput {
+
+ /**
+ * Callback called by the provider manager when model providers are registered.
+ *
+ * @param factory
+ * The model provider factory
+ */
+ void providerRegistered(ITimeGraphModelProviderFactory factory);
+ }
+
+ private static final TimeGraphModelProviderManager INSTANCE = new TimeGraphModelProviderManager();
+
+ private TimeGraphModelProviderManager() {}
+
+ /**
+ * Get the singleton instance of this manager.
+ *
+ * @return The instance
+ */
+ public static TimeGraphModelProviderManager instance() {
+ return INSTANCE;
+ }
+
+ private final Set<ITimeGraphModelProviderFactory> fRegisteredProviderFactories = new LinkedHashSet<>();
+ private final Set<TimeGraphOutput> fRegisteredOutputs = new HashSet<>();
+
+ /**
+ * Register a time graph provider by specifying its
+ * {@link ITimeGraphModelProviderFactory}. If a factory is already registered,
+ * this method will have no effect.
+ *
+ * @param factory
+ * The provider's factory
+ */
+ public synchronized void registerProviderFactory(ITimeGraphModelProviderFactory factory) {
+ boolean added = fRegisteredProviderFactories.add(factory);
+ if (added) {
+ fRegisteredOutputs.forEach(output -> output.providerRegistered(factory));
+ }
+ }
+
+ /**
+ * Register a time graph output to this manager. Upon registration,
+ * notifications will be sent, using {@link TimeGraphOutput#providerRegistered},
+ * for all existing providers. Additional notifications will be sent for future
+ * registered providers.
+ *
+ * @param output
+ * The time graph output to register
+ */
+ public synchronized void registerOutput(TimeGraphOutput output) {
+ /* Send notifications for currently-registered factories */
+ fRegisteredProviderFactories.forEach(factory -> output.providerRegistered(factory));
+ fRegisteredOutputs.add(output);
+ }
+
+ /**
+ * Unregister a previously-registered output, so that it does not receive any
+ * more notifications. Has no effect if the output was not already registered.
+ *
+ * @param output
+ * The output to unregister
+ */
+ public synchronized void unregisterOutput(TimeGraphOutput output) {
+ fRegisteredOutputs.remove(output);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.core.timegraph.model.provider.arrows;
+
+import java.util.concurrent.FutureTask;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
+import org.lttng.scope.tmf2.views.core.TimeRange;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.arrows.TimeGraphArrowRender;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.arrows.TimeGraphArrowSeries;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.tree.TimeGraphTreeRender;
+
+import javafx.beans.property.BooleanProperty;
+import javafx.beans.property.ObjectProperty;
+
+/**
+ * Provider for timegraph arrow series.
+ *
+ * It can be used stand-alone (ie, for testing) but usually would be part of a
+ * {@link org.lttng.scope.tmf2.views.core.timegraph.model.provider.ITimeGraphModelProvider}.
+ *
+ * @author Alexandre Montplaisir
+ */
+public interface ITimeGraphModelArrowProvider {
+
+ /**
+ * Property representing the trace this arrow provider fetches its data for.
+ *
+ * @return The trace property
+ */
+ ObjectProperty<@Nullable ITmfTrace> traceProperty();
+
+ /**
+ * Property indicating if this specific arrow provider is currently enabled
+ * or not.
+ *
+ * Normally the controls should not send queries to this provider if it is
+ * disabled (they would only query this state), but the arrow provider is
+ * free to make use of this property for other reasons.
+ *
+ * @return The enabled property
+ */
+ BooleanProperty enabledProperty();
+
+ /**
+ * Get the arrow series supplied by this arrow provider. A single provider
+ * supplies one and only one arrow series.
+ *
+ * @return The arrow series
+ */
+ TimeGraphArrowSeries getArrowSeries();
+
+ /**
+ * Get a render of arrows from this arrow provider.
+ *
+ * @param treeRender
+ * The tree render for which the query is done
+ * @param timeRange
+ * The time range of the query. The provider may decide to
+ * include arrows partially inside this range, or not.
+ * @param task
+ * An optional task parameter, which can be checked for
+ * cancellation to stop processing at any point and return.
+ * @return The corresponding arrow render
+ */
+ TimeGraphArrowRender getArrowRender(TimeGraphTreeRender treeRender, TimeRange timeRange, @Nullable FutureTask<?> task);
+
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.core.timegraph.model.provider.arrows;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.arrows.TimeGraphArrowSeries;
+
+import javafx.beans.property.BooleanProperty;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.SimpleBooleanProperty;
+import javafx.beans.property.SimpleObjectProperty;
+
+/**
+ * Basic implementation of {@link ITimeGraphModelArrowProvider}. It takes care
+ * of basic definitions of the interface, and lets query definition (the
+ * {@link #getArrowRender} method) up to the subclass.
+ *
+ * @author Alexandre Montplaisir
+ */
+public abstract class TimeGraphModelArrowProvider implements ITimeGraphModelArrowProvider {
+
+ private final ObjectProperty<@Nullable ITmfTrace> fTraceProperty = new SimpleObjectProperty<>(null);
+ private final BooleanProperty fEnabledProperty = new SimpleBooleanProperty(false);
+ private final TimeGraphArrowSeries fArrowSeries;
+
+ /**
+ * Constructor
+ *
+ * @param arrowSeries
+ * The arrow series that will be represented by this arrow
+ * provider
+ */
+ public TimeGraphModelArrowProvider(TimeGraphArrowSeries arrowSeries) {
+ fArrowSeries = arrowSeries;
+ }
+
+ @Override
+ public final ObjectProperty<@Nullable ITmfTrace> traceProperty() {
+ return fTraceProperty;
+ }
+
+ @Override
+ public BooleanProperty enabledProperty() {
+ return fEnabledProperty;
+ }
+
+ @Override
+ public final TimeGraphArrowSeries getArrowSeries() {
+ return fArrowSeries;
+ }
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc. and others
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *******************************************************************************/
+
+@org.eclipse.jdt.annotation.NonNullByDefault
+package org.lttng.scope.tmf2.views.core.timegraph.model.provider.arrows;
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.core.timegraph.model.provider.drawnevents;
+
+import java.util.concurrent.FutureTask;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
+import org.lttng.scope.tmf2.views.core.TimeRange;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.drawnevents.TimeGraphDrawnEventRender;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.drawnevents.TimeGraphDrawnEventSeries;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.tree.TimeGraphTreeRender;
+
+import javafx.beans.property.BooleanProperty;
+import javafx.beans.property.ObjectProperty;
+
+/**
+ * Interface defining providers of drawn events.
+ *
+ * A "drawn event" is a symbol in a timegraph representing one particular
+ * (trace) event. Contrary to a trace event, a drawn event has both horizontal
+ * and vertical coordinates, being time and its corresponding time graph entry.
+ *
+ * @author Alexandre Montplaisir
+ * @see TimeGraphDrawnEventProviderManager
+ */
+public interface ITimeGraphDrawnEventProvider {
+
+ /**
+ * The trace from which the provider will fetch its events. The Property
+ * mechanisms can be used to sync with trace changes elsewhere in the
+ * framework.
+ *
+ * @return The target trace
+ */
+ ObjectProperty<@Nullable ITmfTrace> traceProperty();
+
+ /**
+ * The 'enabled' property of this provider. A provider can be created and
+ * registered to the manager, but considered disabled so that it stops
+ * painting its events.
+ *
+ * For example, if the user can uncheck elements in a list showing all
+ * existing event providers, this could disable said providers without
+ * completely destroying them.
+ *
+ * @return The enabled property
+ */
+ BooleanProperty enabledProperty();
+
+ /**
+ * Return the {@link TimeGraphDrawnEventSeries} provided by this provider. A
+ * single provider should manage a single event series; different providers
+ * should be implemented for different series.
+ *
+ * @return The event series of this provider
+ */
+ TimeGraphDrawnEventSeries getEventSeries();
+
+ /**
+ * Query for a render of drawn events on this provider.
+ *
+ * @param treeRender
+ * The tree render of the timegraph to paint on
+ * @param timeRange
+ * The time range of the timegraph (and trace) to run the query
+ * on.
+ * @param task
+ * If this method is called from within a {@link FutureTask}, it
+ * can optionally be passed here. If the execution is expected to
+ * take a long time, the implementation is suggested to
+ * periodically check this parameter for cancellation.
+ * @return The corresponding event render that contains the result of the
+ * query
+ */
+ TimeGraphDrawnEventRender getEventRender(TimeGraphTreeRender treeRender,
+ TimeRange timeRange, @Nullable FutureTask<?> task);
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.core.timegraph.model.provider.drawnevents;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.drawnevents.TimeGraphDrawnEventSeries;
+
+import javafx.beans.property.BooleanProperty;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.SimpleBooleanProperty;
+import javafx.beans.property.SimpleObjectProperty;
+
+/**
+ * Basic implementation of {@link ITimeGraphDrawnEventProvider}.
+ *
+ * Implementation of the {@link #getEventRender} method is left to subclasses.
+ *
+ * @author Alexandre Montplaisir
+ */
+public abstract class TimeGraphDrawnEventProvider implements ITimeGraphDrawnEventProvider {
+
+ private final ObjectProperty<@Nullable ITmfTrace> fTraceProperty = new SimpleObjectProperty<>(null);
+ private final BooleanProperty fEnabledProperty = new SimpleBooleanProperty(false);
+ private final TimeGraphDrawnEventSeries fDrawnEventSeries;
+
+ /**
+ * Constructor
+ *
+ * @param drawnEventSeries
+ * The event series provided by this provider.
+ */
+ protected TimeGraphDrawnEventProvider(TimeGraphDrawnEventSeries drawnEventSeries) {
+ fDrawnEventSeries = drawnEventSeries;
+ }
+
+ @Override
+ public final ObjectProperty<@Nullable ITmfTrace> traceProperty() {
+ return fTraceProperty;
+ }
+
+ @Override
+ public final BooleanProperty enabledProperty() {
+ return fEnabledProperty;
+ }
+
+ @Override
+ public final TimeGraphDrawnEventSeries getEventSeries() {
+ return fDrawnEventSeries;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.core.timegraph.model.provider.drawnevents;
+
+import java.util.Comparator;
+import java.util.Set;
+import java.util.TreeSet;
+
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableSet;
+
+/**
+ * Manager of {@link ITimeGraphDrawnEventProvider}s.
+ *
+ * @author Alexandre Montplaisir
+ */
+public final class TimeGraphDrawnEventProviderManager {
+
+ private static final Comparator<ITimeGraphDrawnEventProvider> COMPARATOR =
+ Comparator.comparing(provider -> provider.getEventSeries().getSeriesName());
+
+ private static final TimeGraphDrawnEventProviderManager INSTANCE = new TimeGraphDrawnEventProviderManager();
+
+ private final ObservableSet<ITimeGraphDrawnEventProvider> fRegisteredProviders =
+ FXCollections.observableSet(new TreeSet<>(COMPARATOR));
+
+ private TimeGraphDrawnEventProviderManager() {}
+
+ /**
+ * Get the singleton instance.
+ *
+ * @return The instance
+ */
+ public static TimeGraphDrawnEventProviderManager instance() {
+ return INSTANCE;
+ }
+
+ /**
+ * Return the {@link ObservableSet} of registered providers.
+ *
+ * You can use {@link Set#add} and {@link Set#remove} to register and
+ * deregister providers, but also the {@link ObservableSet#addListener}
+ * methods to be notified of provider changes.
+ *
+ * @return The registered providers
+ */
+ public ObservableSet<ITimeGraphDrawnEventProvider> getRegisteredProviders() {
+ return fRegisteredProviders;
+ }
+
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc. and others
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *******************************************************************************/
+
+@org.eclipse.jdt.annotation.NonNullByDefault
+package org.lttng.scope.tmf2.views.core.timegraph.model.provider.drawnevents;
--- /dev/null
+###############################################################################
+# Copyright (c) 2016 EfficiOS Inc., Alexandre Montplaisir
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Eclipse Public License v1.0
+# which accompanies this distribution, and is available at
+# http://www.eclipse.org/legal/epl-v10.html
+###############################################################################
+
+DefaultSortingModeName = Default
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc. and others
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *******************************************************************************/
+
+@org.eclipse.jdt.annotation.NonNullByDefault
+package org.lttng.scope.tmf2.views.core.timegraph.model.provider;
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.core.timegraph.model.provider.states;
+
+import java.util.List;
+import java.util.concurrent.FutureTask;
+import java.util.stream.Collectors;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
+import org.lttng.scope.tmf2.views.core.TimeRange;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.StateDefinition;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.states.TimeGraphStateRender;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.tree.TimeGraphTreeElement;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.tree.TimeGraphTreeRender;
+
+import javafx.beans.property.ObjectProperty;
+
+/**
+ * Provider for timegraph state intervals.
+ *
+ * It can be used stand-alone (ie, for testing) but usually would be part of a
+ * {@link org.lttng.scope.tmf2.views.core.timegraph.model.provider.ITimeGraphModelProvider}.
+ *
+ * @author Alexandre Montplaisir
+ */
+public interface ITimeGraphModelStateProvider {
+
+ /**
+ * Property representing the trace this arrow provider fetches its data for.
+ *
+ * @return The trace property
+ */
+ ObjectProperty<@Nullable ITmfTrace> traceProperty();
+
+ /**
+ * Get an aggregated list of {@link StateDefinition} used in this provider.
+ *
+ * @return The state definitions used in this provider
+ */
+ List<StateDefinition> getStateDefinitions();
+
+ /**
+ * Get a state render for a single tree element
+ *
+ * @param treeElement
+ * The tree element to target
+ * @param timeRange
+ * The time range of the query
+ * @param resolution
+ * The resolution, in timestamp units, of the query. For example,
+ * a resolution of 1000 means the provider does not need to
+ * return states that don't cross at least 1 1000-units borders.
+ * @param task
+ * An optional task parameter, which can be checked for
+ * cancellation to stop processing at any point and return.
+ * @return The corresponding state render for this tree element and settings
+ */
+ TimeGraphStateRender getStateRender(TimeGraphTreeElement treeElement,
+ TimeRange timeRange, long resolution, @Nullable FutureTask<?> task);
+
+ /**
+ * Helper method to fetch all the state renders for all tree elements of a
+ * *tree* render.
+ *
+ * Default implementation simply calls {@link #getStateRender} on each tree
+ * element sequentially, but more advanced providers could override it if
+ * they can provide a faster mechanism.
+ *
+ * @param treeRender
+ * The tree render for which to prepare all the state renders
+ * @param timeRange
+ * The time range of the query
+ * @param resolution
+ * The resolution of the query, see {@link #getStateRender}.
+ * @param task
+ * The optional task parameter which can be used for
+ * cancellation.
+ * @return The corresponding state renders
+ */
+ default List<TimeGraphStateRender> getStateRenders(TimeGraphTreeRender treeRender, TimeRange timeRange, long resolution, @Nullable FutureTask<?> task) {
+ return treeRender.getAllTreeElements().stream()
+ .map(treeElem -> getStateRender(treeElem, timeRange, resolution, task))
+ .collect(Collectors.toList());
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.core.timegraph.model.provider.states;
+
+import java.util.List;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.StateDefinition;
+
+import com.google.common.collect.ImmutableList;
+
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.SimpleObjectProperty;
+
+/**
+ * Basic implementation of {@link ITimeGraphModelStateProvider}.
+ *
+ * @author Alexandre Montplaisir
+ */
+public abstract class TimeGraphModelStateProvider implements ITimeGraphModelStateProvider {
+
+ private final ObjectProperty<@Nullable ITmfTrace> fTraceProperty = new SimpleObjectProperty<>(null);
+
+ private final List<StateDefinition> fStateDefinitions;
+
+ /**
+ * Constructor
+ *
+ * @param stateDefinitions
+ * The state definitions used in this provider
+ */
+ public TimeGraphModelStateProvider(List<StateDefinition> stateDefinitions) {
+ fStateDefinitions = ImmutableList.copyOf(stateDefinitions);
+ }
+
+ @Override
+ public ObjectProperty<@Nullable ITmfTrace> traceProperty() {
+ return fTraceProperty;
+ }
+
+ @Override
+ public List<StateDefinition> getStateDefinitions() {
+ return fStateDefinitions;
+ }
+
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc. and others
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *******************************************************************************/
+
+@org.eclipse.jdt.annotation.NonNullByDefault
+package org.lttng.scope.tmf2.views.core.timegraph.model.provider.states;
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.core.timegraph.model.provider.statesystem;
+
+import java.util.concurrent.FutureTask;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.tmf.core.statesystem.TmfStateSystemAnalysisModule;
+import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
+import org.lttng.scope.tmf2.views.core.TimeRange;
+import org.lttng.scope.tmf2.views.core.timegraph.model.provider.arrows.TimeGraphModelArrowProvider;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.arrows.TimeGraphArrowRender;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.arrows.TimeGraphArrowSeries;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.tree.TimeGraphTreeRender;
+
+import ca.polymtl.dorsal.libdelorean.ITmfStateSystem;
+
+/**
+ * Basic implementation of a {@link TimeGraphModelArrowProvider} backed by a
+ * state system.
+ *
+ * @author Alexandre Montplaisir
+ */
+public abstract class StateSystemModelArrowProvider extends TimeGraphModelArrowProvider {
+
+ private final String fStateSystemModuleId;
+
+ private transient @Nullable ITmfStateSystem fStateSystem = null;
+
+ /**
+ * Constructor
+ *
+ * @param arrowSeries
+ * The arrow series that will be represented by this arrow
+ * provider
+ * @param stateSystemModuleId
+ * The ID of the state system from which the information should
+ * be fetched
+ */
+ public StateSystemModelArrowProvider(TimeGraphArrowSeries arrowSeries,
+ String stateSystemModuleId) {
+ super(arrowSeries);
+ fStateSystemModuleId = stateSystemModuleId;
+
+ /*
+ * Change listener which will take care of keeping the target state
+ * system up to date.
+ */
+ traceProperty().addListener((obs, oldValue, newValue) -> {
+ ITmfTrace trace = newValue;
+ if (trace == null) {
+ fStateSystem = null;
+ return;
+ }
+
+ // FIXME Remove the extra thread once we move to Jabberwocky
+ Thread thread = new Thread(() -> {
+ fStateSystem = TmfStateSystemAnalysisModule.getStateSystem(trace, fStateSystemModuleId);
+ });
+ thread.start();
+ });
+ }
+
+ /**
+ * The state system from which the data should be fetched. This will be kept
+ * in sync with the {@link #traceProperty}.
+ *
+ * @return The target state system. It will be null if the current trace is
+ * null.
+ */
+ protected final @Nullable ITmfStateSystem getStateSystem() {
+ return fStateSystem;
+ }
+
+ @Override
+ public abstract TimeGraphArrowRender getArrowRender(TimeGraphTreeRender treeRender, TimeRange timeRange, @Nullable FutureTask<?> task);
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *******************************************************************************/
+
+package org.lttng.scope.tmf2.views.core.timegraph.model.provider.statesystem;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.WeakHashMap;
+import java.util.function.Function;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.tmf.core.statesystem.TmfStateSystemAnalysisModule;
+import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
+import org.lttng.scope.tmf2.views.core.timegraph.model.provider.TimeGraphModelProvider;
+import org.lttng.scope.tmf2.views.core.timegraph.model.provider.arrows.ITimeGraphModelArrowProvider;
+import org.lttng.scope.tmf2.views.core.timegraph.model.provider.states.ITimeGraphModelStateProvider;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.tree.TimeGraphTreeRender;
+
+import ca.polymtl.dorsal.libdelorean.ITmfStateSystem;
+import ca.polymtl.dorsal.libdelorean.exceptions.StateSystemDisposedException;
+import ca.polymtl.dorsal.libdelorean.interval.ITmfStateInterval;
+
+/**
+ * Basic implementation of a {@link TimeGraphModelProvider} backed by a state
+ * system.
+ *
+ * @author Alexandre Montplaisir
+ */
+public abstract class StateSystemModelProvider extends TimeGraphModelProvider {
+
+ /**
+ * The context of a tree render. Should contain all the information to
+ * generate the corresponding tree render, according to all configuration
+ * options like sorting, filtering etc. specified by the user.
+ */
+ protected static final class TreeRenderContext {
+
+ /** Trace name */
+ public final String traceName;
+ /** State system */
+ public final ITmfStateSystem ss;
+ /** Sorting mode */
+ public final SortingMode sortingMode;
+ /** Filter modes */
+ public final Set<FilterMode> filterModes;
+ /** Full query */
+ public final List<ITmfStateInterval> fullQueryAtRangeStart;
+
+ /**
+ * Constructor
+ *
+ * @param ss
+ * State system
+ * @param sortingMode
+ * Current sorting mode
+ * @param filterModes
+ * Current filter modes
+ * @param fullQueryAtRangeStart
+ * Full query at the start of the time range.
+ */
+ public TreeRenderContext(String traceName,
+ ITmfStateSystem ss,
+ SortingMode sortingMode,
+ Set<FilterMode> filterModes,
+ List<ITmfStateInterval> fullQueryAtRangeStart) {
+ this.traceName = traceName;
+ this.ss = ss;
+ this.sortingMode = sortingMode;
+ this.filterModes = filterModes;
+ this.fullQueryAtRangeStart = fullQueryAtRangeStart;
+ }
+ }
+
+ /**
+ * Class to encapsulate a cached {@link TimeGraphTreeRender}. This render
+ * should never change, except if the number of attributes in the state
+ * system does (for example, if queries were made before the state system
+ * was done building).
+ */
+ private static final class CachedTreeRender {
+
+ public final int nbAttributes;
+ public final TimeGraphTreeRender treeRender;
+
+ public CachedTreeRender(int nbAttributes, TimeGraphTreeRender treeRender) {
+ this.nbAttributes = nbAttributes;
+ this.treeRender = treeRender;
+ }
+ }
+
+ private final String fStateSystemModuleId;
+ private final Function<TreeRenderContext, TimeGraphTreeRender> fTreeRenderFunction;
+
+ private final Map<ITmfStateSystem, CachedTreeRender> fLastTreeRenders = new WeakHashMap<>();
+
+ private transient @Nullable ITmfStateSystem fStateSystem = null;
+
+ /**
+ * Constructor
+ *
+ * @param name
+ * The name of this provider
+ * @param sortingModes
+ * The available sorting modes
+ * @param filterModes
+ * The available filter modes
+ * @param stateProvider
+ * The state provider part of this model provider
+ * @param arrowProviders
+ * The arrow provider(s) supplied by this model provider
+ * @param stateSystemModuleId
+ * ID of the state system from which data will be fetched
+ * @param treeRenderFunction
+ * Function to generate a tree render for a given tree context
+ */
+ public StateSystemModelProvider(String name,
+ @Nullable List<SortingMode> sortingModes,
+ @Nullable List<FilterMode> filterModes,
+ ITimeGraphModelStateProvider stateProvider,
+ @Nullable List<ITimeGraphModelArrowProvider> arrowProviders,
+ String stateSystemModuleId,
+ Function<TreeRenderContext, TimeGraphTreeRender> treeRenderFunction) {
+
+ super(name, sortingModes, filterModes, stateProvider, arrowProviders);
+
+ fStateSystemModuleId = stateSystemModuleId;
+ fTreeRenderFunction = treeRenderFunction;
+
+ /*
+ * Change listener which will take care of keeping the target state
+ * system up to date.
+ */
+ traceProperty().addListener((obs, oldValue, newValue) -> {
+ ITmfTrace trace = newValue;
+ if (trace == null) {
+ fStateSystem = null;
+ return;
+ }
+
+ /*
+ * Set the state system in another thread, so that if it blocks on
+ * waitForInitialization, it does not block the application
+ * thread...
+ *
+ * FIXME We ought to get rid of this blocking in Jabberwocky.
+ */
+ Thread thread = new Thread(() -> {
+ fStateSystem = TmfStateSystemAnalysisModule.getStateSystem(trace, fStateSystemModuleId);
+ });
+ thread.start();
+ });
+ }
+
+ // ------------------------------------------------------------------------
+ // Render generation methods
+ // ------------------------------------------------------------------------
+
+ @Override
+ public @NonNull TimeGraphTreeRender getTreeRender() {
+ ITmfStateSystem ss = fStateSystem;
+ if (ss == null) {
+ /* This trace does not provide the expected state system */
+ return TimeGraphTreeRender.EMPTY_RENDER;
+ }
+
+ CachedTreeRender cachedRender = fLastTreeRenders.get(ss);
+ if (cachedRender != null && cachedRender.nbAttributes == ss.getNbAttributes()) {
+ /* The last render is still valid, we can re-use it */
+ return cachedRender.treeRender;
+ }
+
+ /* First generate the tree render context */
+ List<ITmfStateInterval> fullStateAtStart;
+ try {
+ fullStateAtStart = ss.queryFullState(ss.getStartTime());
+ } catch (StateSystemDisposedException e) {
+ return TimeGraphTreeRender.EMPTY_RENDER;
+ }
+
+ ITmfTrace trace = getTrace();
+ String traceName = (trace == null ? "" : trace.getName()); //$NON-NLS-1$
+
+ TreeRenderContext treeContext = new TreeRenderContext(traceName,
+ ss,
+ getCurrentSortingMode(),
+ getActiveFilterModes(),
+ fullStateAtStart);
+
+ /* Generate a new tree render */
+ TimeGraphTreeRender treeRender = fTreeRenderFunction.apply(treeContext);
+
+ fLastTreeRenders.put(ss, new CachedTreeRender(ss.getNbAttributes(), treeRender));
+ return treeRender;
+ }
+
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *******************************************************************************/
+
+package org.lttng.scope.tmf2.views.core.timegraph.model.provider.statesystem;
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.FutureTask;
+import java.util.function.Function;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.tmf.core.statesystem.TmfStateSystemAnalysisModule;
+import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
+import org.lttng.scope.tmf2.views.core.MathUtils;
+import org.lttng.scope.tmf2.views.core.TimeRange;
+import org.lttng.scope.tmf2.views.core.config.ConfigOption;
+import org.lttng.scope.tmf2.views.core.timegraph.model.provider.states.TimeGraphModelStateProvider;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.ColorDefinition;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.LineThickness;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.StateDefinition;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.states.BasicTimeGraphStateInterval;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.states.MultiStateInterval;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.states.TimeGraphStateInterval;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.states.TimeGraphStateRender;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.tree.TimeGraphTreeElement;
+
+import com.google.common.collect.Iterables;
+
+import ca.polymtl.dorsal.libdelorean.ITmfStateSystem;
+import ca.polymtl.dorsal.libdelorean.exceptions.AttributeNotFoundException;
+import ca.polymtl.dorsal.libdelorean.exceptions.StateSystemDisposedException;
+import ca.polymtl.dorsal.libdelorean.interval.ITmfStateInterval;
+
+/**
+ * Basic implementation of a {@link TimeGraphModelStateProvider} backed by a state
+ * system.
+ *
+ * @author Alexandre Montplaisir
+ */
+public class StateSystemModelStateProvider extends TimeGraphModelStateProvider {
+
+ /**
+ * The context of a single state interval. Should contain all the
+ * information required to generate the state interval in the render (name,
+ * color, etc.)
+ */
+ protected static final class StateIntervalContext {
+
+ /** State system */
+ public final ITmfStateSystem ss;
+ /** Base tree element */
+ public final StateSystemTimeGraphTreeElement baseTreeElement;
+ /** Source interval */
+ public final ITmfStateInterval sourceInterval;
+
+ /**
+ * Constructor
+ *
+ * @param ss
+ * State system
+ * @param baseTreeElement
+ * Tree element for which the data should be fetched. It may
+ * not correspond directly to the state's tree element, a
+ * relative path may be used, for example for additional data
+ * stored in a separate attribute.
+ * @param sourceInterval
+ * The state system interval which will be represented by the
+ * model state interval
+ */
+ public StateIntervalContext(ITmfStateSystem ss,
+ StateSystemTimeGraphTreeElement baseTreeElement,
+ ITmfStateInterval sourceInterval) {
+ this.ss = ss;
+ this.baseTreeElement = baseTreeElement;
+ this.sourceInterval = sourceInterval;
+ }
+ }
+
+ private final String fStateSystemModuleId;
+ private final Function<StateIntervalContext, TimeGraphStateInterval> fIntervalMappingFunction;
+
+ /**
+ * This state system here is not necessarily the same as the one in the
+ * {@link StateSystemModelProvider}!
+ */
+ private transient @Nullable ITmfStateSystem fStateSystem = null;
+
+ /**
+ * Constructor
+ *
+ * TODO Maybe merge the various Functions into a single class?
+ *
+ * @param stateDefinitions
+ * The state definitions used in this provider
+ * @param stateSystemModuleId
+ * The ID of the state system from which to fetch the information
+ * @param stateNameMappingFunction
+ * Mapping function from state interval context to state name
+ * @param labelMappingFunction
+ * Mapping function from state interval context to state label
+ * @param colorMappingFunction
+ * Mapping function from state interval context to state color
+ * @param lineThicknessMappingFunction
+ * Mapping function from state interval context to line thickness
+ * @param propertiesMappingFunction
+ * Mapping function from state interval context to properties
+ */
+ public StateSystemModelStateProvider(
+ List<StateDefinition> stateDefinitions,
+ String stateSystemModuleId,
+ Function<StateIntervalContext, String> stateNameMappingFunction,
+ Function<StateIntervalContext, @Nullable String> labelMappingFunction,
+ Function<StateIntervalContext, ConfigOption<ColorDefinition>> colorMappingFunction,
+ Function<StateIntervalContext, ConfigOption<LineThickness>> lineThicknessMappingFunction,
+ Function<StateIntervalContext, Map<String, String>> propertiesMappingFunction) {
+
+ super(stateDefinitions);
+
+ fStateSystemModuleId = stateSystemModuleId;
+
+ fIntervalMappingFunction = ssCtx -> {
+ return new BasicTimeGraphStateInterval(
+ ssCtx.sourceInterval.getStartTime(),
+ ssCtx.sourceInterval.getEndTime(),
+ ssCtx.baseTreeElement,
+ stateNameMappingFunction.apply(ssCtx),
+ labelMappingFunction.apply(ssCtx),
+ colorMappingFunction.apply(ssCtx),
+ lineThicknessMappingFunction.apply(ssCtx),
+ propertiesMappingFunction.apply(ssCtx));
+ };
+
+ /*
+ * Change listener which will take care of keeping the target state
+ * system up to date.
+ */
+ traceProperty().addListener((obs, oldValue, newValue) -> {
+ ITmfTrace trace = newValue;
+ if (trace == null) {
+ fStateSystem = null;
+ return;
+ }
+
+ // FIXME Remove the extra thread once we move to Jabberwocky
+ Thread thread = new Thread(() -> {
+ fStateSystem = TmfStateSystemAnalysisModule.getStateSystem(trace, fStateSystemModuleId);
+ });
+ thread.start();
+ });
+
+ }
+
+ // ------------------------------------------------------------------------
+ // Render generation methods
+ // ------------------------------------------------------------------------
+
+ @Override
+ public TimeGraphStateRender getStateRender(TimeGraphTreeElement treeElement,
+ TimeRange timeRange, long resolution, @Nullable FutureTask<?> task) {
+
+ ITmfStateSystem ss = fStateSystem;
+ /*
+ * Sometimes ss is null with uninitialized or empty views, just keep the model
+ * empty.
+ */
+ if (ss == null
+ || (task != null && task.isCancelled())
+ /* "Title" entries should be ignored */
+ || !(treeElement instanceof StateSystemTimeGraphTreeElement)) {
+
+ return TimeGraphStateRender.EMPTY_RENDER;
+ }
+ StateSystemTimeGraphTreeElement treeElem = (StateSystemTimeGraphTreeElement) treeElement;
+
+ /* Prepare the state intervals */
+ List<TimeGraphStateInterval> intervals;
+ try {
+ intervals = queryHistoryRange(ss, treeElem,
+ timeRange.getStart(), timeRange.getEnd(), resolution, task);
+ } catch (AttributeNotFoundException | StateSystemDisposedException e) {
+ intervals = Collections.emptyList();
+ }
+
+ return new TimeGraphStateRender(timeRange, treeElement, intervals);
+ }
+
+ private List<TimeGraphStateInterval> queryHistoryRange(ITmfStateSystem ss,
+ StateSystemTimeGraphTreeElement treeElem,
+ final long t1, final long t2, final long resolution,
+ @Nullable FutureTask<?> task)
+ throws AttributeNotFoundException, StateSystemDisposedException {
+
+ /* Validate the parameters. */
+ if (t2 < t1 || resolution <= 0) {
+ throw new IllegalArgumentException(ss.getSSID() + " Start:" + t1 + ", End:" + t2 + ", Resolution:" + resolution); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+
+ final List<TimeGraphStateInterval> modelIntervals = new LinkedList<>();
+ final int attributeQuark = treeElem.getSourceQuark();
+ ITmfStateInterval lastAddedInterval = null;
+
+ /* Actual valid start/end time of the range query. */
+ long tStart = Math.max(t1, ss.getStartTime());
+ long tEnd = Math.min(t2, ss.getCurrentEndTime());
+
+ /*
+ * First, iterate over the "resolution points" and keep all matching
+ * state intervals.
+ */
+ for (long ts = tStart; ts <= tEnd - resolution; ts += resolution) {
+ /*
+ * Skip queries if the corresponding interval was already included
+ */
+ if (lastAddedInterval != null && lastAddedInterval.getEndTime() >= ts) {
+ long nextTOffset = MathUtils.roundToClosestHigherMultiple(lastAddedInterval.getEndTime() - tStart, resolution);
+ long nextTs = tStart + nextTOffset;
+ if (nextTs == ts) {
+ /*
+ * The end time of the last interval happened to be exactly
+ * equal to the next resolution point. We will go to the
+ * resolution point after that then.
+ */
+ ts = nextTs;
+ } else {
+ /* 'ts' will get incremented at next loop */
+ ts = nextTs - resolution;
+ }
+ continue;
+ }
+
+ ITmfStateInterval stateSystemInterval = ss.querySingleState(ts, attributeQuark);
+
+ /*
+ * Only pick the interval if it fills the current resolution range,
+ * from 'ts' to 'ts + resolution' (or 'ts2').
+ */
+ long ts2 = ts + resolution;
+ if (stateSystemInterval.getStartTime() <= ts && stateSystemInterval.getEndTime() >= ts2) {
+ TimeGraphStateInterval interval = ssIntervalToModelInterval(ss, treeElem, stateSystemInterval);
+ modelIntervals.add(interval);
+ lastAddedInterval = stateSystemInterval;
+ }
+ }
+
+ /*
+ * For the very last interval, we'll use ['tEnd - resolution', 'tEnd']
+ * as a range condition instead.
+ */
+ long ts = Math.max(tStart, tEnd - resolution);
+ long ts2 = tEnd;
+ if (lastAddedInterval != null && lastAddedInterval.getEndTime() >= ts) {
+ /* Interval already included */
+ } else {
+ ITmfStateInterval stateSystemInterval = ss.querySingleState(ts, attributeQuark);
+ if (stateSystemInterval.getStartTime() <= ts && stateSystemInterval.getEndTime() >= ts2) {
+ TimeGraphStateInterval interval = ssIntervalToModelInterval(ss, treeElem, stateSystemInterval);
+ modelIntervals.add(interval);
+ }
+ }
+
+ /*
+ * 'modelIntervals' now contains all the "real" intervals we will want
+ * to display in the view. Poly-filla the holes in between each using
+ * multi-state intervals.
+ */
+ if (modelIntervals.size() < 2) {
+ return modelIntervals;
+ }
+
+ List<TimeGraphStateInterval> filledIntervals = new LinkedList<>();
+ /*
+ * Add the first real interval. There might be a multi-state at the
+ * beginning.
+ */
+ long firstRealIntervalStartTime = modelIntervals.get(0).getStartTime();
+ if (firstRealIntervalStartTime > tStart) {
+ filledIntervals.add(new MultiStateInterval(tStart, firstRealIntervalStartTime - 1, treeElem));
+ }
+ filledIntervals.add(modelIntervals.get(0));
+
+ for (int i = 1; i < modelIntervals.size(); i++) {
+ TimeGraphStateInterval interval1 = modelIntervals.get(i - 1);
+ TimeGraphStateInterval interval2 = modelIntervals.get(i);
+ long bound1 = interval1.getEndTime();
+ long bound2 = interval2.getStartTime();
+
+ /* (we've already inserted 'interval1' on the previous loop.) */
+ if (bound1 + 1 != bound2) {
+ TimeGraphStateInterval multiStateInterval = new MultiStateInterval(bound1 + 1, bound2 - 1, treeElem);
+ filledIntervals.add(multiStateInterval);
+ }
+ filledIntervals.add(interval2);
+ }
+
+ /* Add a multi-state at the end too, if needed */
+ long lastRealIntervalEndTime = Iterables.getLast(modelIntervals).getEndTime();
+ if (lastRealIntervalEndTime < t2) {
+ filledIntervals.add(new MultiStateInterval(lastRealIntervalEndTime + 1, t2, treeElem));
+ }
+
+ return filledIntervals;
+ }
+
+ private TimeGraphStateInterval ssIntervalToModelInterval(ITmfStateSystem ss,
+ StateSystemTimeGraphTreeElement treeElem, ITmfStateInterval interval) {
+ StateIntervalContext siCtx = new StateIntervalContext(ss, treeElem, interval);
+ return fIntervalMappingFunction.apply(siCtx);
+ }
+
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *******************************************************************************/
+
+package org.lttng.scope.tmf2.views.core.timegraph.model.provider.statesystem;
+
+import java.util.List;
+
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.tree.TimeGraphTreeElement;
+
+/**
+ * Implementation of a {@link TimeGraphTreeElement}Â specific for use by
+ * {@link StateSystemModelProvider}. It links a state system quark to the tree
+ * element.
+ *
+ * @author Alexandre Montplaisir
+ */
+public class StateSystemTimeGraphTreeElement extends TimeGraphTreeElement {
+
+ private final int fSourceQuark;
+
+ /**
+ * Constructor
+ *
+ * @param name
+ * The name this tree element should have.
+ * @param children
+ * The children tree elements. You can pass an empty list for no
+ * children.
+ * @param sourceQuark
+ * The state system quark wrapped by this tree element
+ */
+ public StateSystemTimeGraphTreeElement(String name,
+ List<TimeGraphTreeElement> children,
+ int sourceQuark) {
+ super(name, children);
+ fSourceQuark = sourceQuark;
+ }
+
+ /**
+ * Get the quark wrapped by this tree element.
+ *
+ * @return The source quark
+ */
+ public int getSourceQuark() {
+ return fSourceQuark;
+ }
+
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc. and others
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *******************************************************************************/
+
+@org.eclipse.jdt.annotation.NonNullByDefault
+package org.lttng.scope.tmf2.views.core.timegraph.model.provider.statesystem;
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *******************************************************************************/
+
+package org.lttng.scope.tmf2.views.core.timegraph.model.render;
+
+import java.util.Objects;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * Basic headless color definition, using RGB(A) values.
+ *
+ * @author Alexandre Montplaisir
+ */
+public class ColorDefinition {
+
+ /** Minimum value for the parameters */
+ public static final int MIN = 0;
+ /** Maximum value for the parameters */
+ public static final int MAX = 255;
+
+ /** Red RGB component */
+ public final int fRed;
+ /** Green RGB component */
+ public final int fGreen;
+ /** Blue RGB component */
+ public final int fBlue;
+ /** Alpha component */
+ public final int fAlpha;
+
+ /**
+ * Specify a color by RGB values, using maximum (full) alpha.
+ *
+ * @param red
+ * Red component, from 0 to 255
+ * @param green
+ * Green component, from 0 to 255
+ * @param blue
+ * Blue component, from 0 to 255
+ */
+ public ColorDefinition(int red, int green, int blue) {
+ this(red, green, blue, MAX);
+ }
+
+ /**
+ * Specify a color by RGBA values.
+ *
+ * @param red
+ * Red component, from 0 to 255
+ * @param green
+ * Green component, from 0 to 255
+ * @param blue
+ * Blue component, from 0 to 255
+ * @param alpha
+ * Alpha (opacity) component, from 0 to 255
+ */
+ public ColorDefinition(int red, int green, int blue, int alpha) {
+ checkValue(red);
+ checkValue(green);
+ checkValue(blue);
+ checkValue(alpha);
+
+ fRed = red;
+ fGreen = green;
+ fBlue = blue;
+ fAlpha = alpha;
+ }
+
+ private static void checkValue(int value) throws IllegalArgumentException {
+ if (value < MIN || value > MAX) {
+ throw new IllegalArgumentException();
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(fRed, fGreen, fBlue, fAlpha);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ ColorDefinition other = (ColorDefinition) obj;
+ return (fRed == other.fRed
+ && fGreen == other.fGreen
+ && fBlue == other.fBlue
+ && fAlpha == other.fAlpha);
+ }
+
+ @Override
+ public String toString() {
+ return IntStream.of(fRed, fGreen, fBlue, fAlpha)
+ .mapToObj(String::valueOf)
+ .collect(Collectors.joining(", ", "[", "]")); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.lttng.scope.tmf2.views.core.timegraph.model.render;
+
+/**
+ * Definition of UI colors, from https://flatuicolors.com/ .
+ */
+@SuppressWarnings("javadoc")
+public interface FlatUIColors {
+
+ ColorDefinition TURQUOISE = new ColorDefinition( 26, 188, 156);
+ ColorDefinition DARK_TURQUOISE = new ColorDefinition( 22, 160, 133);
+ ColorDefinition GREEN = new ColorDefinition( 46, 204, 113);
+ ColorDefinition DARK_GREEN = new ColorDefinition( 39, 174, 96);
+ ColorDefinition BLUE = new ColorDefinition( 52, 152, 219);
+ ColorDefinition DARK_BLUE = new ColorDefinition( 41, 128, 185);
+ ColorDefinition PURPLE = new ColorDefinition(155, 89, 182);
+ ColorDefinition DARK_PURPLE = new ColorDefinition(142, 68, 173);
+ ColorDefinition BLUE_GRAY = new ColorDefinition( 52, 73, 94);
+ ColorDefinition DARK_BLUE_GRAY = new ColorDefinition( 44, 62, 80);
+ ColorDefinition YELLOW = new ColorDefinition(241, 196, 15);
+ ColorDefinition DARK_YELLOW = new ColorDefinition(243, 156, 18);
+ ColorDefinition ORANGE = new ColorDefinition(230, 126, 34);
+ ColorDefinition DARK_ORANGE = new ColorDefinition(211, 84, 0);
+ ColorDefinition RED = new ColorDefinition(231, 76, 60);
+ ColorDefinition DARK_RED = new ColorDefinition(192, 57, 43);
+ ColorDefinition VERY_LIGHT_GRAY = new ColorDefinition(236, 240, 241);
+ ColorDefinition LIGHT_GRAY = new ColorDefinition(189, 195, 199);
+ ColorDefinition GRAY = new ColorDefinition(149, 165, 166);
+ ColorDefinition DARK_GRAY = new ColorDefinition(127, 140, 141);
+
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.core.timegraph.model.render;
+
+/**
+ * Headless definitions of possible values of state interval line thicknesses
+ */
+public enum LineThickness {
+
+ /** Normal, full thickness */
+ NORMAL,
+ /**
+ * Small thickness, should be between {@link #NORMAL} and {@link #TINY} and
+ * distinguishable from those two.
+ */
+ SMALL,
+ /**
+ * Tiny line thickness. The line should be as small as possible but still
+ * have the color distinguishable.
+ */
+ TINY
+
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.core.timegraph.model.render;
+
+import org.lttng.scope.tmf2.views.core.config.ConfigOption;
+
+/**
+ * Class defining the UI reprsentation of a state, as well as default values for
+ * them.
+ *
+ * @author Alexandre Montplaisir
+ */
+public class StateDefinition {
+
+ private final String fName;
+ private final ConfigOption<ColorDefinition> fColor;
+ private final ConfigOption<LineThickness> fLineThickness;
+
+ /**
+ * Build a new state definition. Re-use the same object where the same
+ * definition apply, so that the same configuration property is used.
+ *
+ * @param name
+ * The name of the definition
+ * @param defaultColor
+ * The default color to use
+ * @param defaultLineThickness
+ * The default line thickness to use
+ */
+ public StateDefinition(String name, ColorDefinition defaultColor, LineThickness defaultLineThickness) {
+ fName = name;
+ fColor = new ConfigOption<>(defaultColor);
+ fLineThickness = new ConfigOption<>(defaultLineThickness);
+ }
+
+ /**
+ * Get the name of this definition.
+ *
+ * @return The name
+ */
+ public String getName() {
+ return fName;
+ }
+
+ /**
+ * Get the color property of this definition.
+ *
+ * @return The color property
+ */
+ public ConfigOption<ColorDefinition> getColor() {
+ return fColor;
+ }
+
+ /**
+ * Get the line thickness property of this definition.
+ *
+ * @return The line thickness property
+ */
+ public ConfigOption<LineThickness> getLineThickness() {
+ return fLineThickness;
+ }
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *******************************************************************************/
+
+package org.lttng.scope.tmf2.views.core.timegraph.model.render;
+
+import java.util.Objects;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.tree.TimeGraphTreeElement;
+
+/**
+ * A time graph event is a virtual representation of a location on the time
+ * graph, defined by its timestamp and tree element.
+ *
+ * It does not have a directly corresponding UI representation, but is used as
+ * an intermediate construct in intervals and drawn events, for example.
+ *
+ * @author Alexandre Montplaisir
+ */
+public class TimeGraphEvent {
+
+ private final long fTimestamp;
+ private final TimeGraphTreeElement fTreeElement;
+
+ /**
+ * Constructor
+ *
+ * @param timestamp
+ * The timestamp of the event
+ * @param treeElement
+ * The tree element to which this even belong
+ */
+ public TimeGraphEvent(long timestamp, TimeGraphTreeElement treeElement) {
+ fTimestamp = timestamp;
+ fTreeElement = treeElement;
+ }
+
+ /**
+ * Get the timestamp of this event.
+ *
+ * @return The timestamp
+ */
+ public long getTimestamp() {
+ return fTimestamp;
+ }
+
+ /**
+ * Get the tree element of this event.
+ *
+ * @return The tree element
+ */
+ public TimeGraphTreeElement getTreeElement() {
+ return fTreeElement;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(fTimestamp, fTreeElement);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ TimeGraphEvent other = (TimeGraphEvent) obj;
+ return (fTimestamp == other.fTimestamp
+ && Objects.equals(fTreeElement, other.fTreeElement));
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2016-2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.core.timegraph.model.render.arrows;
+
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.TimeGraphEvent;
+
+/**
+ * Model representation of a timegraph arrow.
+ *
+ * An arrow links two {@link TimeGraphEvent}s, and has a direction (a start
+ * event and a end event). The two events can belong to different tree elements,
+ * or the same one.
+ *
+ * @author Alexandre Montplaisir
+ */
+public class TimeGraphArrow {
+
+ private final TimeGraphEvent fStartEvent;
+ private final TimeGraphEvent fEndEvent;
+ private final TimeGraphArrowSeries fArrowSeries;
+
+ /**
+ * Constructor
+ *
+ * @param startEvent
+ * The start event of this arrow
+ * @param endEvent
+ * The end event of this arrow. The drawn arrowhead should be on
+ * this side
+ * @param series
+ * The series to which the arrow belongs. This series contains
+ * all the styling information.
+ */
+ public TimeGraphArrow(TimeGraphEvent startEvent, TimeGraphEvent endEvent, TimeGraphArrowSeries series) {
+ fStartEvent = startEvent;
+ fEndEvent = endEvent;
+ fArrowSeries = series;
+ }
+
+ /**
+ * Get the start event of this arrow.
+ *
+ * @return The start event
+ */
+ public TimeGraphEvent getStartEvent() {
+ return fStartEvent;
+ }
+
+ /**
+ * Get the end event of this arrow.
+ *
+ * @return The end event
+ */
+ public TimeGraphEvent getEndEvent() {
+ return fEndEvent;
+ }
+
+ /**
+ * Get the series of this arrow.
+ *
+ * @return The arrow series
+ */
+ public TimeGraphArrowSeries getArrowSeries() {
+ return fArrowSeries;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (C) 2016-2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.core.timegraph.model.render.arrows;
+
+import java.util.Collection;
+import java.util.Collections;
+
+import org.lttng.scope.tmf2.views.core.TimeRange;
+
+import com.google.common.collect.ImmutableList;
+
+/**
+ * Render of time graph arrows, containing all possible arrows of a single
+ * series for a given time range.
+ *
+ * @author Alexandre Montplaisir
+ */
+public class TimeGraphArrowRender {
+
+ /** Empty arrow render, can be used instead of a null value */
+ public static final TimeGraphArrowRender EMPTY_RENDER =
+ new TimeGraphArrowRender(TimeRange.of(0, 0), Collections.EMPTY_LIST);
+
+ private final TimeRange fTimeRange;
+ private final Collection<TimeGraphArrow> fArrows;
+
+ /**
+ * Constructor
+ *
+ * @param range
+ * Time range of this arrow render. For reference only, should
+ * probably match the time range of the query that created this
+ * render.
+ * @param arrows
+ * The arrows contained in this render.
+ */
+ public TimeGraphArrowRender(TimeRange range, Iterable<TimeGraphArrow> arrows) {
+ fTimeRange = range;
+ fArrows = ImmutableList.copyOf(arrows);
+ }
+
+ /**
+ * Get the time range of this arrow render.
+ *
+ * @return The time range
+ */
+ public TimeRange getTimeRange() {
+ return fTimeRange;
+ }
+
+ /**
+ * Get the arrows contained in this render.
+ *
+ * @return The arrows
+ */
+ public Collection<TimeGraphArrow> getArrows() {
+ return fArrows;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (C) 2016-2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.core.timegraph.model.render.arrows;
+
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.ColorDefinition;
+
+/**
+ * Definition of a time graph arrow series.
+ *
+ * It contains all the styling information (name, color, line type, etc.) for
+ * the arrows part of this series. It does not however contain the arrows
+ * themselves, it is just the definition. The arrows will keep reference of what
+ * series they belong to.
+ *
+ * @author Alexandre Montplaisir
+ */
+public class TimeGraphArrowSeries {
+
+ /**
+ * Headless definition of the style of the arrow line.
+ */
+ public enum LineStyle {
+ /** Full line */
+ FULL,
+ /** Line composed of dotsf */
+ DOTTED,
+ /** Line composed of dashes */
+ DASHED;
+ }
+
+ private final String fSeriesName;
+ private final ColorDefinition fColor;
+ private final LineStyle fLineStyle;
+
+ /**
+ * Constructor
+ *
+ * @param seriesName
+ * Name of this series
+ * @param color
+ * Suggested color for arrows of this series
+ * @param lineStyle
+ * Suggested line style for arrows of this series
+ */
+ public TimeGraphArrowSeries(String seriesName, ColorDefinition color, LineStyle lineStyle) {
+ fSeriesName = seriesName;
+ fColor = color;
+ fLineStyle = lineStyle;
+ }
+
+ /**
+ * Get the name of this series.
+ *
+ * @return The series's name
+ */
+ public String getSeriesName() {
+ return fSeriesName;
+ }
+
+ /**
+ * Get the suggested color of this series.
+ *
+ * @return The series's color
+ */
+ public ColorDefinition getColor() {
+ return fColor;
+ }
+
+ /**
+ * Get the suggested line style of this series.
+ *
+ * @return The series's line style
+ */
+ public LineStyle getLineStyle() {
+ return fLineStyle;
+ }
+
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc. and others
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *******************************************************************************/
+
+@org.eclipse.jdt.annotation.NonNullByDefault
+package org.lttng.scope.tmf2.views.core.timegraph.model.render.arrows;
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *******************************************************************************/
+
+package org.lttng.scope.tmf2.views.core.timegraph.model.render.drawnevents;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.function.Supplier;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.TimeGraphEvent;
+
+/**
+ * Model representation of a drawn event.
+ *
+ * A drawn event is a UI representation of a single {@link TimeGraphEvent}, with
+ * a given style.
+ *
+ * Additional properties can also be part of the drawn event, for example to be
+ * shown on mouse-over.
+ *
+ * @author Alexandre Montplaisir
+ */
+public class TimeGraphDrawnEvent {
+
+ private final TimeGraphEvent fTimeGraphEvent;
+ private final TimeGraphDrawnEventSeries fEventSeries;
+ private final @Nullable Supplier<Map<String, String>> fPropertySupplier;
+
+ /**
+ * Constructor
+ *
+ * @param event
+ * Time graph event (location)
+ * @param eventSeries
+ * Series of this event, which contains all the styling
+ * information
+ * @param propertySupplier
+ * Supplier of additional properties, which can be accessed only
+ * the first time {@link #getProperties()} is called.
+ */
+ public TimeGraphDrawnEvent(TimeGraphEvent event,
+ TimeGraphDrawnEventSeries eventSeries,
+ @Nullable Supplier<Map<String, String>> propertySupplier) {
+ fTimeGraphEvent = event;
+ fEventSeries = eventSeries;
+ fPropertySupplier = propertySupplier;
+ }
+
+ /**
+ * Get the timegraph event wrapped by this drawn event.
+ *
+ * @return The time graph event
+ */
+ public TimeGraphEvent getEvent() {
+ return fTimeGraphEvent;
+ }
+
+ /**
+ * Get the event series of this drawn event.
+ *
+ * @return The event's series
+ */
+ public TimeGraphDrawnEventSeries getEventSeries() {
+ return fEventSeries;
+ }
+
+ /**
+ * Get the additional properties of this drawn event.
+ *
+ * @return The event's properties
+ */
+ public Map<String, String> getProperties() {
+ Supplier<Map<String, String>> supplier = fPropertySupplier;
+ if (supplier == null) {
+ return Collections.EMPTY_MAP;
+ }
+ return supplier.get();
+ }
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *******************************************************************************/
+
+package org.lttng.scope.tmf2.views.core.timegraph.model.render.drawnevents;
+
+import java.util.Collection;
+
+import org.lttng.scope.tmf2.views.core.TimeRange;
+
+import com.google.common.collect.ImmutableList;
+
+/**
+ * Render of time graph drawn events. It can cover several (usually all) tree
+ * elements, unlike the TimeGraphStateRender which covers only a single element.
+ *
+ * @author Alexandre Montplaisir
+ */
+public class TimeGraphDrawnEventRender {
+
+ private final TimeRange fTimeRange;
+ private final Collection<TimeGraphDrawnEvent> fEvents;
+
+ /**
+ * Constructor
+ *
+ * @param timeRange
+ * The time range of this draw event render. Should usually match
+ * the time range of the query that created this render.
+ * @param events
+ * The drawn events contained in this render
+ */
+ public TimeGraphDrawnEventRender(TimeRange timeRange,
+ Iterable<TimeGraphDrawnEvent> events) {
+
+ fTimeRange = timeRange;
+ fEvents = ImmutableList.copyOf(events);
+ }
+
+ /**
+ * Get the time range of this render
+ *
+ * @return The time range
+ */
+ public TimeRange getTimeRange() {
+ return fTimeRange;
+ }
+
+ /**
+ * Get the drawn events that are part of this render.
+ *
+ * @return The drawn events
+ */
+ public Collection<TimeGraphDrawnEvent> getEvents() {
+ return fEvents;
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2016-2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.core.timegraph.model.render.drawnevents;
+
+import org.lttng.scope.tmf2.views.core.config.ConfigOption;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.ColorDefinition;
+
+/**
+ * Definition of a time graph arrow series.
+ *
+ * It contains all the styling information (symbols, color, etc.) for the events
+ * that are part of this series. It does not contain the events themselves, the
+ * events will keep a reference to their series instead.
+ *
+ * @author Alexandre Montplaisir
+ */
+public class TimeGraphDrawnEventSeries {
+
+ /**
+ * Headless symbol style definitions
+ */
+ public enum SymbolStyle {
+ /** Circle */
+ CIRCLE,
+ /** Cross */
+ CROSS,
+ /** Star */
+ STAR,
+ /** Square */
+ SQUARE,
+ /** Diamond */
+ DIAMOND,
+ /** Triangle */
+ TRIANGLE;
+ }
+
+ private final String fSeriesName;
+ private final ConfigOption<ColorDefinition> fColor;
+ private final ConfigOption<SymbolStyle> fSymbolStyle;
+
+ /**
+ * Constructor.
+ *
+ * The parameters passed as {@link ConfigOption}s can be modified at
+ * runtime.
+ *
+ * @param seriesName
+ * Name of this event series
+ * @param color
+ * Color for this event series.
+ * @param symbolStyle
+ * Symbol style for this series
+ */
+ public TimeGraphDrawnEventSeries(String seriesName,
+ ConfigOption<ColorDefinition> color,
+ ConfigOption<SymbolStyle> symbolStyle) {
+
+ fSeriesName = seriesName;
+ fColor = color;
+ fSymbolStyle = symbolStyle;
+ }
+
+ /**
+ * Get the name of this series
+ *
+ * @return This series's name
+ */
+ public String getSeriesName() {
+ return fSeriesName;
+ }
+
+ /**
+ * Get the configurable color of this series.
+ *
+ * @return This series's color
+ */
+ public ConfigOption<ColorDefinition> getColor() {
+ return fColor;
+ }
+
+ /**
+ * Get the configurable symbol style of this series.
+ *
+ * @return This series's symbol style
+ */
+ public ConfigOption<SymbolStyle> getSymbolStyle() {
+ return fSymbolStyle;
+ }
+
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc. and others
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *******************************************************************************/
+
+@org.eclipse.jdt.annotation.NonNullByDefault
+package org.lttng.scope.tmf2.views.core.timegraph.model.render.drawnevents;
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc. and others
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *******************************************************************************/
+
+@org.eclipse.jdt.annotation.NonNullByDefault
+package org.lttng.scope.tmf2.views.core.timegraph.model.render;
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *******************************************************************************/
+
+package org.lttng.scope.tmf2.views.core.timegraph.model.render.states;
+
+import java.util.Map;
+import java.util.Objects;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.lttng.scope.tmf2.views.core.config.ConfigOption;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.ColorDefinition;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.LineThickness;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.TimeGraphEvent;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.tree.TimeGraphTreeElement;
+
+import com.google.common.base.MoreObjects;
+
+/**
+ * Basic implementation of {@link TimeGraphStateInterval}.
+ *
+ * @author Alexandre Montplaisir
+ */
+public class BasicTimeGraphStateInterval implements TimeGraphStateInterval {
+
+ private final TimeGraphEvent fStartEvent;
+ private final TimeGraphEvent fEndEvent;
+
+ private final String fStateName;
+ private final @Nullable String fLabel;
+ private final ConfigOption<ColorDefinition> fColor;
+ private final ConfigOption<LineThickness> fLineThickness;
+
+ private final Map<String, String> fProperties;
+
+ /**
+ * Constructor.
+ *
+ * It requires supplying the start time, end time, and tree element. The
+ * corresponding {@link TimeGraphEvent} will be created from those.
+ *
+ * @param start
+ * Start time
+ * @param end
+ * End time
+ * @param treeElement
+ * Tree element of this interval
+ * @param stateName
+ * State name
+ * @param label
+ * Label, see {@link #getLabel()}
+ * @param color
+ * Color
+ * @param lineThickness
+ * Line thickness
+ * @param properties
+ * Properties
+ */
+ public BasicTimeGraphStateInterval(long start,
+ long end,
+ TimeGraphTreeElement treeElement,
+ String stateName,
+ @Nullable String label,
+ ConfigOption<ColorDefinition> color,
+ ConfigOption<LineThickness> lineThickness,
+ Map<String, String> properties) {
+
+ if (start > end || start < 0 || end < 0) {
+ throw new IllegalArgumentException();
+ }
+
+ fStartEvent = new TimeGraphEvent(start, treeElement);
+ fEndEvent = new TimeGraphEvent(end, treeElement);
+
+ fStateName = stateName;
+ fLabel = label;
+ fColor = color;
+ fLineThickness = lineThickness;
+ fProperties = properties;
+ }
+
+ @Override
+ public TimeGraphEvent getStartEvent() {
+ return fStartEvent;
+ }
+
+ @Override
+ public TimeGraphEvent getEndEvent() {
+ return fEndEvent;
+ }
+
+ @Override
+ public String getStateName() {
+ return fStateName;
+ }
+
+ @Override
+ public @Nullable String getLabel() {
+ return fLabel;
+ }
+
+ @Override
+ public ConfigOption<ColorDefinition> getColorDefinition() {
+ return fColor;
+ }
+
+ @Override
+ public ConfigOption<LineThickness> getLineThickness() {
+ return fLineThickness;
+ }
+
+ @Override
+ public boolean isMultiState() {
+ return false;
+ }
+
+ @Override
+ public Map<String, String> getProperties() {
+ return fProperties;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(fStartEvent, fEndEvent, fStateName, fLabel, fColor, fLineThickness);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ BasicTimeGraphStateInterval other = (BasicTimeGraphStateInterval) obj;
+ return (Objects.equals(fStartEvent, other.fStartEvent)
+ && Objects.equals(fEndEvent, other.fEndEvent)
+ && Objects.equals(fStateName, other.fStateName)
+ && Objects.equals(fLabel, other.fLabel)
+ && Objects.equals(fColor, other.fColor)
+ && fLineThickness == other.fLineThickness);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("start", fStartEvent.getTimestamp()) //$NON-NLS-1$
+ .add("end", fEndEvent.getTimestamp()) //$NON-NLS-1$
+ .add("name", fStateName) //$NON-NLS-1$
+ .toString();
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.core.timegraph.model.render.states;
+
+import java.util.Collections;
+
+import org.lttng.scope.tmf2.views.core.config.ConfigOption;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.ColorDefinition;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.LineThickness;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.tree.TimeGraphTreeElement;
+
+/**
+ * Dummy interval model object representing a "multi-state", which means a case
+ * where more than one state exists for a given pixel.
+ *
+ * @author Alexandre Montplaisir
+ */
+public final class MultiStateInterval extends BasicTimeGraphStateInterval {
+
+ /** Configuration option for the line thickness of multi-state intervals */
+ public static final ConfigOption<LineThickness> MULTI_STATE_THICKNESS_OPTION = new ConfigOption<>(LineThickness.NORMAL);
+
+ private static final String MULTI_STATE_NAME = "Multi-state"; //$NON-NLS-1$
+ private static final ColorDefinition MULTI_STATE_COLOR = new ColorDefinition(0, 0, 0);
+ private static final ConfigOption<ColorDefinition> MULTI_STATE_COLOR_OPTION = new ConfigOption<>(MULTI_STATE_COLOR);
+
+ /**
+ * Constructor
+ *
+ * @param startTime
+ * Start time
+ * @param endTime
+ * End time
+ * @param treeElement
+ * The tree element to which this interval is associated
+ */
+ public MultiStateInterval(long startTime, long endTime, TimeGraphTreeElement treeElement) {
+ super(startTime,
+ endTime,
+ treeElement,
+ MULTI_STATE_NAME,
+ /* Label */
+ null,
+ MULTI_STATE_COLOR_OPTION,
+ MULTI_STATE_THICKNESS_OPTION,
+ Collections.emptyMap());
+ }
+
+ @Override
+ public boolean isMultiState() {
+ return true;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.core.timegraph.model.render.states;
+
+import java.util.Map;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.lttng.scope.tmf2.views.core.config.ConfigOption;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.ColorDefinition;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.LineThickness;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.TimeGraphEvent;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.tree.TimeGraphTreeElement;
+
+/**
+ * Base interface for time graph state interval, which is defined by a start
+ * time, end time, and a state.
+ *
+ * @author Alexandre Montplaisir
+ */
+public interface TimeGraphStateInterval {
+
+ /**
+ * Return the start event of this interval.
+ *
+ * @return The start event
+ */
+ TimeGraphEvent getStartEvent();
+
+ /**
+ * Return the end event of this interval.
+ *
+ * @return The end event
+ */
+ TimeGraphEvent getEndEvent();
+
+ /**
+ * Get the name of the state represented by this interval.
+ *
+ * @return The state name
+ */
+ String getStateName();
+
+ /**
+ * Get the label of this interval. This means the text that is meant to be
+ * displayed alongside the interval's color. It may or may not be the same
+ * as the {@link #getStateName()}.
+ *
+ * @return The optional label of this interval
+ */
+ @Nullable String getLabel();
+
+ /**
+ * Get the color suggested for this interval.
+ *
+ * @return The color of this interval
+ */
+ ConfigOption<ColorDefinition> getColorDefinition();
+
+ /**
+ * Get the suggested line thickness of this interval.
+ *
+ * @return The line thickness
+ */
+ ConfigOption<LineThickness> getLineThickness();
+
+ /**
+ * Indicate if this interval represents multiple states, or a single one.
+ *
+ * @return If this interval is a multi-state one
+ */
+ boolean isMultiState();
+
+ /**
+ * Get the properties associated with this state interval. This is extra,
+ * generic data that can be attached to the interval. Views can display
+ * those in a tooltip, for example.
+ *
+ * @return The additional properties.
+ */
+ Map<String, String> getProperties();
+
+ /**
+ * Get the start time of this interval, which is effectively the timestamp
+ * of the start event.
+ *
+ * @return The start time
+ */
+ default long getStartTime() {
+ return getStartEvent().getTimestamp();
+ }
+
+ /**
+ * Get the end time of this interval, which is effectively the timestamp of
+ * the end event.
+ *
+ * @return The end time
+ */
+ default long getEndTime() {
+ return getEndEvent().getTimestamp();
+ }
+
+ /**
+ * Get the duration of this interval, effectively end time minus start time.
+ *
+ * @return The duration
+ */
+ default long getDuration() {
+ return (getEndTime() - getStartTime());
+ }
+
+ /**
+ * Get the tree element of this interval's start and end events.
+ *
+ * @return The tree element
+ */
+ default TimeGraphTreeElement getTreeElement() {
+ return getStartEvent().getTreeElement();
+ }
+
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *******************************************************************************/
+
+package org.lttng.scope.tmf2.views.core.timegraph.model.render.states;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.lttng.scope.tmf2.views.core.TimeRange;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.tree.TimeGraphTreeElement;
+
+import com.google.common.collect.ImmutableList;
+
+/**
+ * "Segment" of a time graph, representing the states of a single tree element
+ * for a given time range.
+ *
+ * @author Alexandre Montplaisir
+ */
+public class TimeGraphStateRender {
+
+ /** Non-null reference to a dummy/empty render */
+ public static final TimeGraphStateRender EMPTY_RENDER = new TimeGraphStateRender(TimeRange.of(0, 0), TimeGraphTreeElement.DUMMY_ELEMENT, Collections.emptyList());
+
+ private final TimeRange fTimeRange;
+ private final TimeGraphTreeElement fTreeElement;
+ private final List<TimeGraphStateInterval> fStateIntervals;
+
+ /**
+ * Constructor
+ *
+ * @param timeRange
+ * The time range of this state render. This is for informative
+ * use mostly, and usually matches the time range that was
+ * requested for the query that generated this render.
+ * @param treeElement
+ * This render contains state intervals of this single tree
+ * element
+ * @param stateIntervals
+ * The state intervals that are part of this render. Their range
+ * should normally match the 'timeRange' parameter.
+ */
+ public TimeGraphStateRender(TimeRange timeRange,
+ TimeGraphTreeElement treeElement,
+ List<TimeGraphStateInterval> stateIntervals) {
+
+ fTimeRange = timeRange;
+ fTreeElement = treeElement;
+ fStateIntervals = ImmutableList.copyOf(stateIntervals);
+ }
+
+ /**
+ * Get the time range of this interval.
+ *
+ * @return The time range
+ */
+ public TimeRange getTimeRange() {
+ return fTimeRange;
+ }
+
+ /**
+ * Get the tree element to which the intervals of this render belongs.
+ *
+ * @return The tree element
+ */
+ public TimeGraphTreeElement getTreeElement() {
+ return fTreeElement;
+ }
+
+ /**
+ * Get the state intervals that are part of this state render
+ *
+ * @return The state intervals
+ */
+ public List<TimeGraphStateInterval> getStateIntervals() {
+ return fStateIntervals;
+ }
+
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc. and others
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *******************************************************************************/
+
+@org.eclipse.jdt.annotation.NonNullByDefault
+package org.lttng.scope.tmf2.views.core.timegraph.model.render.states;
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *******************************************************************************/
+
+package org.lttng.scope.tmf2.views.core.timegraph.model.render.tree;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Predicate;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.tmf.core.event.ITmfEvent;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableList;
+
+/**
+ * The "tree element" is the unit of a timegraph represented by a single line.
+ * State intervals are aligned with tree elements to represent the states of the
+ * attribute represented by its tree element.
+ *
+ * Tree elements can have children, which allows representing them as a tree
+ * structure. At the visualization layer, sub-trees could be allowed to be
+ * expanded/collapsed, which can then change the number of visible tree elements
+ * in the timegraph.
+ *
+ * @author Alexandre Montplaisir
+ */
+public class TimeGraphTreeElement {
+
+ /** Non-null reference to a dummy element */
+ public static final TimeGraphTreeElement DUMMY_ELEMENT = new TimeGraphTreeElement("Dummy", Collections.emptyList()); //$NON-NLS-1$
+
+ private final String fName;
+ private final List<TimeGraphTreeElement> fChildElements;
+
+ /**
+ * Constructor, build a tree element by specifying its name and children
+ * elements.
+ *
+ * @param name
+ * The name this tree element should have.
+ * @param children
+ * The children tree elements. You can pass an empty list for no
+ * children.
+ */
+ public TimeGraphTreeElement(String name, List<TimeGraphTreeElement> children) {
+ fName = name;
+ fChildElements = ImmutableList.copyOf(children);
+ }
+
+ /**
+ * Get the name of this tree element.
+ *
+ * @return The element's name
+ */
+ public String getName() {
+ return fName;
+ }
+
+ /**
+ * Get the child elements of this tree element.
+ *
+ * @return The child elements
+ */
+ public List<TimeGraphTreeElement> getChildElements() {
+ return fChildElements;
+ }
+
+ /**
+ * Determine if and how this tree element corresponds to a component of a
+ * trace event.
+ *
+ * For example, if this tree element represents "CPU #2", then the predicate
+ * should return true for all trace events belonging to CPU #2.
+ *
+ * The method returns null if this tree element does not correspond to a
+ * particular aspect of trace events.
+ *
+ * @return The event matching predicate, if there is one
+ */
+ public @Nullable Predicate<ITmfEvent> getEventMatching() {
+ /* Sub-classes can override */
+ return null;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(fName, fChildElements);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ TimeGraphTreeElement other = (TimeGraphTreeElement) obj;
+ return Objects.equals(fName, other.fName)
+ && Objects.equals(fChildElements, other.fChildElements);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("fName", fName) //$NON-NLS-1$
+ .add("fChildElements", fChildElements.toString()) //$NON-NLS-1$
+ .toString();
+ }
+
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *******************************************************************************/
+
+package org.lttng.scope.tmf2.views.core.timegraph.model.render.tree;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.lttng.scope.common.core.StreamUtils.StreamFlattener;
+
+/**
+ * Render of a tree of the timegraph. Contains the tree elements that compose
+ * the current tree.
+ *
+ * In a timegraph, the "tree" part is usually shown on the left-hand side, and
+ * lists the tree elements, which represent attributes of a model. A tree render
+ * is a "snapshot" of this tree that is valid for a given timestamp or
+ * timerange.
+ *
+ * Some timegraphs may use a tree that is valid for the whole time range of a
+ * trace. Other timegraphs may display a different tree for different parts of
+ * the trace.
+ *
+ * @author Alexandre Montplaisir
+ */
+public class TimeGraphTreeRender {
+
+ /**
+ * A static reference to an empty render, which can be used to represent an
+ * uninitialized state for example (by comparing with ==).
+ */
+ public static final TimeGraphTreeRender EMPTY_RENDER = new TimeGraphTreeRender(TimeGraphTreeElement.DUMMY_ELEMENT);
+
+ private final TimeGraphTreeElement fRootElement;
+
+ /**
+ * Constructor
+ *
+ * @param rootElement
+ * The root element of the tree
+ */
+ public TimeGraphTreeRender(TimeGraphTreeElement rootElement) {
+ fRootElement = rootElement;
+ }
+
+ /**
+ * Return the root element of this tree.
+ *
+ * @return The root element
+ */
+ public TimeGraphTreeElement getRootElement() {
+ return fRootElement;
+ }
+
+ /**
+ * Get a flattened view of all the tree elements in this render.
+ *
+ * This should also contains all the child elements that are also contained
+ * in each element's {@link TimeGraphTreeElement#getChildElements()}. It can
+ * be used to run an action on all elements of a render.
+ *
+ * @return A list of all the tree elements
+ */
+ public List<TimeGraphTreeElement> getAllTreeElements() {
+ StreamFlattener<TimeGraphTreeElement> flattener = new StreamFlattener<>(i -> i.getChildElements().stream());
+ return flattener.flatten(getRootElement()).collect(Collectors.toList());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(fRootElement);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ TimeGraphTreeRender other = (TimeGraphTreeRender) obj;
+ return (Objects.equals(fRootElement, other.fRootElement));
+ }
+
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc. and others
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *******************************************************************************/
+
+@org.eclipse.jdt.annotation.NonNullByDefault
+package org.lttng.scope.tmf2.views.core.timegraph.model.render.tree;
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *******************************************************************************/
+
+package org.lttng.scope.tmf2.views.core.timegraph.view;
+
+import org.lttng.scope.tmf2.views.core.TimeRange;
+import org.lttng.scope.tmf2.views.core.context.ViewGroupContext;
+import org.lttng.scope.tmf2.views.core.timegraph.control.TimeGraphModelControl;
+
+/**
+ * Base class for time graph view objects.
+ *
+ * A view is attached to a {@link TimeGraphModelControl} (passed at the
+ * constructor), which will then call the view's method accordingly to
+ * reposition, repaint, etc. the timegraph according to actions taken elsewhere
+ * in the framework.
+ *
+ * @author Alexandre Montplaisir
+ */
+public abstract class TimeGraphModelView {
+
+ private final TimeGraphModelControl fControl;
+
+ /**
+ * Constructor. Build a new view by specifying its corresponding control.
+ * You will most probably want to call
+ * {@link TimeGraphModelControl#attachView} afterwards, this is not done
+ * automatically!
+ *
+ * @param control
+ * The control that will manage this view
+ */
+ public TimeGraphModelView(TimeGraphModelControl control) {
+ fControl = control;
+ }
+
+ /**
+ * Retrieve the control managing this view.
+ *
+ * @return The model control
+ */
+ public final TimeGraphModelControl getControl() {
+ return fControl;
+ }
+
+ /**
+ * Dispose this view
+ */
+ public final void dispose() {
+ disposeImpl();
+ }
+
+ /**
+ * Get the view context of this view/control.
+ *
+ * @return The current view context
+ */
+ public final ViewGroupContext getViewContext() {
+ return getControl().getViewContext();
+ }
+
+ // ------------------------------------------------------------------------
+ // Template methods
+ // ------------------------------------------------------------------------
+
+ /**
+ * Abstract implementation of a dispose method. Sub-classes should clean up
+ * the state they add to the base view class.
+ */
+ protected abstract void disposeImpl();
+
+ /**
+ * Request the timegraph to be completely cleared of its contents, for
+ * example when no trace is opened at all.
+ */
+ public abstract void clear();
+
+ /**
+ * This should be called whenever the visible window moves, including zoom
+ * level changes.
+ *
+ * @param newVisibleRange
+ * The range to where the view should be seeked
+ */
+ public abstract void seekVisibleRange(TimeRange newVisibleRange);
+
+ /**
+ * Draw a new selection rectangle. The previous one, if any, will be
+ * removed.
+ *
+ * @param selectionRange
+ * The selection that should be drawn
+ */
+ public abstract void drawSelection(TimeRange selectionRange);
+
+}
--- /dev/null
+package org.lttng.scope.tmf2.views.core.timegraph.view.json;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.states.TimeGraphStateRender;
+
+import com.google.common.base.Charsets;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+final class RenderToJson {
+
+ private RenderToJson() {}
+
+ private static final boolean PRETTY_PRINT = true;
+ private static final int VERSION = 1;
+
+ private static final String VERSION_KEY = "version"; //$NON-NLS-1$
+ private static final String INTERVALS_KEY = "intervals"; //$NON-NLS-1$
+
+ private static final String TREE_ELEMENT_KEY = "elem"; //$NON-NLS-1$
+ private static final String START_TIME_KEY = "start"; //$NON-NLS-1$
+ private static final String END_TIME_KEY = "end"; //$NON-NLS-1$
+ private static final String STATE_NAME_KEY = "state"; //$NON-NLS-1$
+ private static final String COLOR_KEY = "color"; //$NON-NLS-1$
+
+ private static final Path OUTPUT_FILE = Paths.get("/home/alexandre/json-output"); //$NON-NLS-1$
+ static {
+ try {
+ if (Files.exists(OUTPUT_FILE)) {
+ Files.delete(OUTPUT_FILE);
+ }
+ Files.createFile(OUTPUT_FILE);
+ } catch (IOException e) {
+ }
+ }
+
+ private static final Gson GSON;
+ static {
+ if (PRETTY_PRINT) {
+ GSON = new GsonBuilder().setPrettyPrinting().create();
+ } else {
+ GSON = new Gson();
+ }
+ }
+
+ public static void printRenderTo1(List<TimeGraphStateRender> renders) {
+ String json = GSON.toJson(renders);
+ try (Writer bw = Files.newBufferedWriter(OUTPUT_FILE, Charsets.UTF_8)) {
+ bw.write(json);
+ bw.flush();
+ } catch (IOException e1) {
+ }
+ }
+
+ public static void printRenderTo(List<TimeGraphStateRender> renders) {
+ try (Writer bw = Files.newBufferedWriter(OUTPUT_FILE, Charsets.UTF_8)) {
+ JSONObject root = new JSONObject();
+ root.put(VERSION_KEY, VERSION);
+
+ JSONArray intervalsRoot = new JSONArray();
+ root.put(INTERVALS_KEY, intervalsRoot);
+
+ renders.stream()
+ .flatMap(render -> render.getStateIntervals().stream())
+ .forEach(interval -> {
+ try {
+ JSONObject intervalObject = new JSONObject();
+ intervalObject.put(TREE_ELEMENT_KEY, interval.getStartEvent().getTreeElement().getName());
+ intervalObject.put(START_TIME_KEY, interval.getStartEvent().getTimestamp());
+ intervalObject.put(END_TIME_KEY, interval.getEndEvent().getTimestamp());
+ intervalObject.put(STATE_NAME_KEY, interval.getStateName());
+ intervalObject.put(COLOR_KEY, interval.getColorDefinition().toString());
+
+ intervalsRoot.put(intervalObject);
+ } catch (JSONException e) {
+ /* Skip this interval */
+ }
+ });
+
+ String json = (PRETTY_PRINT ? root.toString(1) : root.toString());
+ bw.write(json);
+ bw.flush();
+
+ } catch (JSONException | IOException e) {
+ }
+ }
+
+}
--- /dev/null
+package org.lttng.scope.tmf2.views.core.timegraph.view.json;
+
+import java.util.List;
+
+import org.lttng.scope.tmf2.views.core.TimeRange;
+import org.lttng.scope.tmf2.views.core.timegraph.control.TimeGraphModelControl;
+import org.lttng.scope.tmf2.views.core.timegraph.model.provider.ITimeGraphModelProvider;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.states.TimeGraphStateRender;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.tree.TimeGraphTreeRender;
+import org.lttng.scope.tmf2.views.core.timegraph.view.TimeGraphModelView;
+
+public class TimeGraphJsonOutput extends TimeGraphModelView {
+
+ public TimeGraphJsonOutput(TimeGraphModelControl control) {
+ super(control);
+ }
+
+ @Override
+ public void disposeImpl() {
+ }
+
+ @Override
+ public void clear() {
+ // TODO
+ }
+
+ @Override
+ public void seekVisibleRange(TimeRange newVisibleRange) {
+ /* Generate JSON for the visible area */
+ ITimeGraphModelProvider provider = getControl().getModelRenderProvider();
+
+ TimeGraphTreeRender treeRender = provider.getTreeRender();
+ List<TimeGraphStateRender> stateRenders = provider.getStateProvider().getStateRenders(treeRender,
+ newVisibleRange, 1, null);
+
+ RenderToJson.printRenderTo(stateRenders);
+ }
+
+ @Override
+ public void drawSelection(TimeRange selectionRange) {
+ // TODO NYI
+ }
+
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc. and others
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *******************************************************************************/
+
+@org.eclipse.jdt.annotation.NonNullByDefault
+package org.lttng.scope.tmf2.views.core.timegraph.view.json;
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc. and others
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *******************************************************************************/
+
+@org.eclipse.jdt.annotation.NonNullByDefault
+package org.lttng.scope.tmf2.views.core.timegraph.view;
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
+ <attributes>
+ <attribute name="annotationpath" value="/org.lttng.scope.common.core/annotations"/>
+ </attributes>
+ </classpathentry>
+ <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins">
+ <attributes>
+ <attribute name="annotationpath" value="/org.lttng.scope.common.core/annotations"/>
+ </attributes>
+ </classpathentry>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>org.lttng.scope.tmf2.views.ui</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.ManifestBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.SchemaBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.api.tools.apiAnalysisBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.pde.PluginNature</nature>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ <nature>org.eclipse.pde.api.tools.apiAnalysisNature</nature>
+ </natures>
+</projectDescription>
--- /dev/null
+eclipse.preferences.version=1
+encoding/<project>=UTF-8
--- /dev/null
+eclipse.preferences.version=1
+line.separator=\n
--- /dev/null
+eclipse.preferences.version=1
+org.eclipse.jdt.core.codeComplete.argumentPrefixes=
+org.eclipse.jdt.core.codeComplete.argumentSuffixes=
+org.eclipse.jdt.core.codeComplete.fieldPrefixes=f
+org.eclipse.jdt.core.codeComplete.fieldSuffixes=
+org.eclipse.jdt.core.codeComplete.localPrefixes=
+org.eclipse.jdt.core.codeComplete.localSuffixes=
+org.eclipse.jdt.core.codeComplete.staticFieldPrefixes=
+org.eclipse.jdt.core.codeComplete.staticFieldSuffixes=
+org.eclipse.jdt.core.codeComplete.staticFinalFieldPrefixes=
+org.eclipse.jdt.core.codeComplete.staticFinalFieldSuffixes=
+org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=enabled
+org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=info
+org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull
+org.eclipse.jdt.core.compiler.annotation.nonnull.secondary=
+org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault
+org.eclipse.jdt.core.compiler.annotation.nonnullbydefault.secondary=
+org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable
+org.eclipse.jdt.core.compiler.annotation.nullable.secondary=
+org.eclipse.jdt.core.compiler.annotation.nullanalysis=enabled
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.8
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.doc.comment.support=enabled
+org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=error
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
+org.eclipse.jdt.core.compiler.problem.comparingIdentical=error
+org.eclipse.jdt.core.compiler.problem.deadCode=error
+org.eclipse.jdt.core.compiler.problem.deprecation=warning
+org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
+org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=enabled
+org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
+org.eclipse.jdt.core.compiler.problem.emptyStatement=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=error
+org.eclipse.jdt.core.compiler.problem.fallthroughCase=error
+org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled
+org.eclipse.jdt.core.compiler.problem.fieldHiding=error
+org.eclipse.jdt.core.compiler.problem.finalParameterBound=error
+org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=error
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=error
+org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=error
+org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=disabled
+org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=error
+org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=error
+org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=error
+org.eclipse.jdt.core.compiler.problem.invalidJavadoc=warning
+org.eclipse.jdt.core.compiler.problem.invalidJavadocTags=enabled
+org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsDeprecatedRef=enabled
+org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsNotVisibleRef=enabled
+org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsVisibility=protected
+org.eclipse.jdt.core.compiler.problem.localVariableHiding=error
+org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=error
+org.eclipse.jdt.core.compiler.problem.missingDefaultCase=error
+org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=error
+org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=enabled
+org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=error
+org.eclipse.jdt.core.compiler.problem.missingJavadocComments=warning
+org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsVisibility=protected
+org.eclipse.jdt.core.compiler.problem.missingJavadocTagDescription=all_standard_tags
+org.eclipse.jdt.core.compiler.problem.missingJavadocTags=warning
+org.eclipse.jdt.core.compiler.problem.missingJavadocTagsMethodTypeParameters=disabled
+org.eclipse.jdt.core.compiler.problem.missingJavadocTagsOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=protected
+org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=error
+org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled
+org.eclipse.jdt.core.compiler.problem.missingSerialVersion=error
+org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=error
+org.eclipse.jdt.core.compiler.problem.noEffectAssignment=error
+org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=error
+org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=warning
+org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning
+org.eclipse.jdt.core.compiler.problem.nonnullTypeVariableFromLegacyInvocation=info
+org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=warning
+org.eclipse.jdt.core.compiler.problem.nullReference=error
+org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error
+org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning
+org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=error
+org.eclipse.jdt.core.compiler.problem.parameterAssignment=error
+org.eclipse.jdt.core.compiler.problem.pessimisticNullAnalysisForFreeTypeVariables=warning
+org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=error
+org.eclipse.jdt.core.compiler.problem.potentialNullReference=error
+org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=warning
+org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning
+org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning
+org.eclipse.jdt.core.compiler.problem.redundantNullCheck=error
+org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=error
+org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=error
+org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore
+org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=error
+org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled
+org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=error
+org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled
+org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
+org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=enabled
+org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
+org.eclipse.jdt.core.compiler.problem.typeParameterHiding=error
+org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=disabled
+org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
+org.eclipse.jdt.core.compiler.problem.unclosedCloseable=error
+org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore
+org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=error
+org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentType=warning
+org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentTypeStrict=disabled
+org.eclipse.jdt.core.compiler.problem.unlikelyEqualsArgumentType=info
+org.eclipse.jdt.core.compiler.problem.unnecessaryElse=error
+org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=error
+org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=warning
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=disabled
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=disabled
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore
+org.eclipse.jdt.core.compiler.problem.unusedImport=warning
+org.eclipse.jdt.core.compiler.problem.unusedLabel=error
+org.eclipse.jdt.core.compiler.problem.unusedLocal=error
+org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=ignore
+org.eclipse.jdt.core.compiler.problem.unusedParameter=warning
+org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled
+org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning
+org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=warning
+org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=error
+org.eclipse.jdt.core.compiler.source=1.8
+org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines=2147483647
+org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_assignment=0
+org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
+org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80
+org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0
+org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
+org.eclipse.jdt.core.formatter.alignment_for_expressions_in_for_loop_header=0
+org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0
+org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
+org.eclipse.jdt.core.formatter.alignment_for_parameterized_type_references=0
+org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=80
+org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
+org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_type_arguments=0
+org.eclipse.jdt.core.formatter.alignment_for_type_parameters=0
+org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16
+org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
+org.eclipse.jdt.core.formatter.blank_lines_after_package=1
+org.eclipse.jdt.core.formatter.blank_lines_before_field=0
+org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
+org.eclipse.jdt.core.formatter.blank_lines_before_imports=1
+org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1
+org.eclipse.jdt.core.formatter.blank_lines_before_method=1
+org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
+org.eclipse.jdt.core.formatter.blank_lines_before_package=0
+org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
+org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1
+org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_lambda_body=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
+org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
+org.eclipse.jdt.core.formatter.comment.format_block_comments=true
+org.eclipse.jdt.core.formatter.comment.format_header=false
+org.eclipse.jdt.core.formatter.comment.format_html=true
+org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
+org.eclipse.jdt.core.formatter.comment.format_line_comments=true
+org.eclipse.jdt.core.formatter.comment.format_source_code=true
+org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true
+org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
+org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
+org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=insert
+org.eclipse.jdt.core.formatter.comment.line_length=80
+org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true
+org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true
+org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false
+org.eclipse.jdt.core.formatter.compact_else_if=true
+org.eclipse.jdt.core.formatter.continuation_indentation=2
+org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
+org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off
+org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on
+org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
+org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
+org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
+org.eclipse.jdt.core.formatter.indent_empty_lines=false
+org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
+org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
+org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
+org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=false
+org.eclipse.jdt.core.formatter.indentation.size=4
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_enum_constant=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
+org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
+org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow=insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert
+org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
+org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
+org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
+org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
+org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.join_lines_in_comments=true
+org.eclipse.jdt.core.formatter.join_wrapped_lines=false
+org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
+org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
+org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false
+org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
+org.eclipse.jdt.core.formatter.lineSplit=250
+org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
+org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
+org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
+org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1
+org.eclipse.jdt.core.formatter.parentheses_positions_in_annotation=common_lines
+org.eclipse.jdt.core.formatter.parentheses_positions_in_catch_clause=common_lines
+org.eclipse.jdt.core.formatter.parentheses_positions_in_enum_constant_declaration=common_lines
+org.eclipse.jdt.core.formatter.parentheses_positions_in_for_statment=common_lines
+org.eclipse.jdt.core.formatter.parentheses_positions_in_if_while_statement=common_lines
+org.eclipse.jdt.core.formatter.parentheses_positions_in_lambda_declaration=common_lines
+org.eclipse.jdt.core.formatter.parentheses_positions_in_method_delcaration=common_lines
+org.eclipse.jdt.core.formatter.parentheses_positions_in_method_invocation=common_lines
+org.eclipse.jdt.core.formatter.parentheses_positions_in_switch_statement=common_lines
+org.eclipse.jdt.core.formatter.parentheses_positions_in_try_clause=common_lines
+org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true
+org.eclipse.jdt.core.formatter.tabulation.char=space
+org.eclipse.jdt.core.formatter.tabulation.size=4
+org.eclipse.jdt.core.formatter.use_on_off_tags=false
+org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
+org.eclipse.jdt.core.formatter.wrap_before_assignment_operator=false
+org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
+org.eclipse.jdt.core.formatter.wrap_before_conditional_operator=true
+org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true
+org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true
+org.eclipse.jdt.core.javaFormatter=org.eclipse.jdt.core.defaultJavaFormatter
--- /dev/null
+eclipse.preferences.version=1
+editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
+formatter_profile=_tmf-style
+formatter_settings_version=12
+org.eclipse.jdt.ui.exception.name=e
+org.eclipse.jdt.ui.gettersetter.use.is=true
+org.eclipse.jdt.ui.keywordthis=false
+org.eclipse.jdt.ui.overrideannotation=true
+sp_cleanup.add_default_serial_version_id=true
+sp_cleanup.add_generated_serial_version_id=false
+sp_cleanup.add_missing_annotations=false
+sp_cleanup.add_missing_deprecated_annotations=true
+sp_cleanup.add_missing_methods=false
+sp_cleanup.add_missing_nls_tags=false
+sp_cleanup.add_missing_override_annotations=true
+sp_cleanup.add_missing_override_annotations_interface_methods=true
+sp_cleanup.add_serial_version_id=false
+sp_cleanup.always_use_blocks=true
+sp_cleanup.always_use_parentheses_in_expressions=false
+sp_cleanup.always_use_this_for_non_static_field_access=false
+sp_cleanup.always_use_this_for_non_static_method_access=false
+sp_cleanup.convert_functional_interfaces=false
+sp_cleanup.convert_to_enhanced_for_loop=false
+sp_cleanup.correct_indentation=false
+sp_cleanup.format_source_code=false
+sp_cleanup.format_source_code_changes_only=false
+sp_cleanup.insert_inferred_type_arguments=false
+sp_cleanup.make_local_variable_final=false
+sp_cleanup.make_parameters_final=false
+sp_cleanup.make_private_fields_final=true
+sp_cleanup.make_type_abstract_if_missing_method=false
+sp_cleanup.make_variable_declarations_final=false
+sp_cleanup.never_use_blocks=false
+sp_cleanup.never_use_parentheses_in_expressions=true
+sp_cleanup.on_save_use_additional_actions=true
+sp_cleanup.organize_imports=true
+sp_cleanup.qualify_static_field_accesses_with_declaring_class=false
+sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
+sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
+sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
+sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
+sp_cleanup.remove_private_constructors=true
+sp_cleanup.remove_redundant_type_arguments=false
+sp_cleanup.remove_trailing_whitespaces=true
+sp_cleanup.remove_trailing_whitespaces_all=true
+sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
+sp_cleanup.remove_unnecessary_casts=false
+sp_cleanup.remove_unnecessary_nls_tags=false
+sp_cleanup.remove_unused_imports=false
+sp_cleanup.remove_unused_local_variables=false
+sp_cleanup.remove_unused_private_fields=true
+sp_cleanup.remove_unused_private_members=false
+sp_cleanup.remove_unused_private_methods=true
+sp_cleanup.remove_unused_private_types=true
+sp_cleanup.sort_members=false
+sp_cleanup.sort_members_all=false
+sp_cleanup.use_anonymous_class_creation=false
+sp_cleanup.use_blocks=true
+sp_cleanup.use_blocks_only_for_return_and_throw=false
+sp_cleanup.use_lambda=true
+sp_cleanup.use_parentheses_in_expressions=false
+sp_cleanup.use_this_for_non_static_field_access=false
+sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
+sp_cleanup.use_this_for_non_static_method_access=false
+sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true
--- /dev/null
+ANNOTATION_ELEMENT_TYPE_ADDED_METHOD_WITHOUT_DEFAULT_VALUE=Error
+ANNOTATION_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error
+ANNOTATION_ELEMENT_TYPE_REMOVED_FIELD=Error
+ANNOTATION_ELEMENT_TYPE_REMOVED_METHOD=Error
+ANNOTATION_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error
+API_COMPONENT_ELEMENT_TYPE_REMOVED_API_TYPE=Error
+API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_API_TYPE=Error
+API_COMPONENT_ELEMENT_TYPE_REMOVED_REEXPORTED_TYPE=Error
+API_COMPONENT_ELEMENT_TYPE_REMOVED_TYPE=Error
+API_USE_SCAN_FIELD_SEVERITY=Error
+API_USE_SCAN_METHOD_SEVERITY=Error
+API_USE_SCAN_TYPE_SEVERITY=Error
+CLASS_ELEMENT_TYPE_ADDED_METHOD=Error
+CLASS_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error
+CLASS_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error
+CLASS_ELEMENT_TYPE_CHANGED_CONTRACTED_SUPERINTERFACES_SET=Error
+CLASS_ELEMENT_TYPE_CHANGED_DECREASE_ACCESS=Error
+CLASS_ELEMENT_TYPE_CHANGED_NON_ABSTRACT_TO_ABSTRACT=Error
+CLASS_ELEMENT_TYPE_CHANGED_NON_FINAL_TO_FINAL=Error
+CLASS_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error
+CLASS_ELEMENT_TYPE_REMOVED_CONSTRUCTOR=Error
+CLASS_ELEMENT_TYPE_REMOVED_FIELD=Error
+CLASS_ELEMENT_TYPE_REMOVED_METHOD=Error
+CLASS_ELEMENT_TYPE_REMOVED_SUPERCLASS=Error
+CLASS_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error
+CLASS_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error
+CONSTRUCTOR_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error
+CONSTRUCTOR_ELEMENT_TYPE_CHANGED_DECREASE_ACCESS=Error
+CONSTRUCTOR_ELEMENT_TYPE_CHANGED_VARARGS_TO_ARRAY=Error
+CONSTRUCTOR_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error
+ENUM_ELEMENT_TYPE_CHANGED_CONTRACTED_SUPERINTERFACES_SET=Error
+ENUM_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error
+ENUM_ELEMENT_TYPE_REMOVED_ENUM_CONSTANT=Error
+ENUM_ELEMENT_TYPE_REMOVED_FIELD=Error
+ENUM_ELEMENT_TYPE_REMOVED_METHOD=Error
+ENUM_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error
+FIELD_ELEMENT_TYPE_ADDED_VALUE=Error
+FIELD_ELEMENT_TYPE_CHANGED_DECREASE_ACCESS=Error
+FIELD_ELEMENT_TYPE_CHANGED_FINAL_TO_NON_FINAL_STATIC_CONSTANT=Error
+FIELD_ELEMENT_TYPE_CHANGED_NON_FINAL_TO_FINAL=Error
+FIELD_ELEMENT_TYPE_CHANGED_NON_STATIC_TO_STATIC=Error
+FIELD_ELEMENT_TYPE_CHANGED_STATIC_TO_NON_STATIC=Error
+FIELD_ELEMENT_TYPE_CHANGED_TYPE=Error
+FIELD_ELEMENT_TYPE_CHANGED_VALUE=Error
+FIELD_ELEMENT_TYPE_REMOVED_TYPE_ARGUMENT=Error
+FIELD_ELEMENT_TYPE_REMOVED_VALUE=Error
+ILLEGAL_EXTEND=Warning
+ILLEGAL_IMPLEMENT=Warning
+ILLEGAL_INSTANTIATE=Warning
+ILLEGAL_OVERRIDE=Warning
+ILLEGAL_REFERENCE=Warning
+INTERFACE_ELEMENT_TYPE_ADDED_DEFAULT_METHOD=Ignore
+INTERFACE_ELEMENT_TYPE_ADDED_FIELD=Error
+INTERFACE_ELEMENT_TYPE_ADDED_METHOD=Error
+INTERFACE_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error
+INTERFACE_ELEMENT_TYPE_ADDED_SUPER_INTERFACE_WITH_METHODS=Error
+INTERFACE_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error
+INTERFACE_ELEMENT_TYPE_CHANGED_CONTRACTED_SUPERINTERFACES_SET=Error
+INTERFACE_ELEMENT_TYPE_CHANGED_TYPE_CONVERSION=Error
+INTERFACE_ELEMENT_TYPE_REMOVED_FIELD=Error
+INTERFACE_ELEMENT_TYPE_REMOVED_METHOD=Error
+INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_MEMBER=Error
+INTERFACE_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error
+INVALID_ANNOTATION=Warning
+INVALID_JAVADOC_TAG=Warning
+INVALID_REFERENCE_IN_SYSTEM_LIBRARIES=Warning
+LEAK_EXTEND=Warning
+LEAK_FIELD_DECL=Warning
+LEAK_IMPLEMENT=Warning
+LEAK_METHOD_PARAM=Warning
+LEAK_METHOD_RETURN_TYPE=Warning
+METHOD_ELEMENT_TYPE_ADDED_RESTRICTIONS=Error
+METHOD_ELEMENT_TYPE_ADDED_TYPE_PARAMETER=Error
+METHOD_ELEMENT_TYPE_CHANGED_DECREASE_ACCESS=Error
+METHOD_ELEMENT_TYPE_CHANGED_NON_ABSTRACT_TO_ABSTRACT=Error
+METHOD_ELEMENT_TYPE_CHANGED_NON_FINAL_TO_FINAL=Error
+METHOD_ELEMENT_TYPE_CHANGED_NON_STATIC_TO_STATIC=Error
+METHOD_ELEMENT_TYPE_CHANGED_STATIC_TO_NON_STATIC=Error
+METHOD_ELEMENT_TYPE_CHANGED_VARARGS_TO_ARRAY=Error
+METHOD_ELEMENT_TYPE_REMOVED_ANNOTATION_DEFAULT_VALUE=Error
+METHOD_ELEMENT_TYPE_REMOVED_TYPE_PARAMETER=Error
+MISSING_EE_DESCRIPTIONS=Ignore
+TYPE_PARAMETER_ELEMENT_TYPE_ADDED_CLASS_BOUND=Error
+TYPE_PARAMETER_ELEMENT_TYPE_ADDED_INTERFACE_BOUND=Error
+TYPE_PARAMETER_ELEMENT_TYPE_CHANGED_CLASS_BOUND=Error
+TYPE_PARAMETER_ELEMENT_TYPE_CHANGED_INTERFACE_BOUND=Error
+TYPE_PARAMETER_ELEMENT_TYPE_REMOVED_CLASS_BOUND=Error
+TYPE_PARAMETER_ELEMENT_TYPE_REMOVED_INTERFACE_BOUND=Error
+UNUSED_PROBLEM_FILTERS=Warning
+automatically_removed_unused_problem_filters=false
+changed_execution_env=Error
+eclipse.preferences.version=1
+incompatible_api_component_version=Error
+incompatible_api_component_version_include_major_without_breaking_change=Disabled
+incompatible_api_component_version_include_minor_without_api_change=Disabled
+incompatible_api_component_version_report_major_without_breaking_change=Warning
+incompatible_api_component_version_report_minor_without_api_change=Warning
+invalid_since_tag_version=Error
+malformed_since_tag=Error
+missing_since_tag=Error
+report_api_breakage_when_major_version_incremented=Disabled
+report_resolution_errors_api_component=Warning
--- /dev/null
+compilers.f.unresolved-features=1
+compilers.f.unresolved-plugins=1
+compilers.incompatible-environment=1
+compilers.p.build=1
+compilers.p.build.bin.includes=0
+compilers.p.build.encodings=2
+compilers.p.build.java.compiler=2
+compilers.p.build.java.compliance=1
+compilers.p.build.missing.output=2
+compilers.p.build.output.library=1
+compilers.p.build.source.library=0
+compilers.p.build.src.includes=0
+compilers.p.deprecated=1
+compilers.p.discouraged-class=1
+compilers.p.internal=1
+compilers.p.missing-packages=1
+compilers.p.missing-version-export-package=2
+compilers.p.missing-version-import-package=2
+compilers.p.missing-version-require-bundle=2
+compilers.p.no-required-att=0
+compilers.p.not-externalized-att=1
+compilers.p.unknown-attribute=1
+compilers.p.unknown-class=1
+compilers.p.unknown-element=1
+compilers.p.unknown-identifier=1
+compilers.p.unknown-resource=1
+compilers.p.unresolved-ex-points=0
+compilers.p.unresolved-import=0
+compilers.s.create-docs=false
+compilers.s.doc-folder=doc
+compilers.s.open-tags=1
+eclipse.preferences.version=1
--- /dev/null
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: %Bundle-Name
+Bundle-Vendor: %Bundle-Vendor
+Bundle-Version: 0.2.0.qualifier
+Bundle-Localization: plugin
+Bundle-SymbolicName: org.lttng.scope.tmf2.views.ui;singleton:=true
+Bundle-Activator: org.lttng.scope.tmf2.views.ui.activator.internal.Activator
+Bundle-ActivationPolicy: lazy
+Bundle-RequiredExecutionEnvironment: JavaSE-1.8
+Require-Bundle: org.eclipse.core.runtime,
+ org.eclipse.ui,
+ org.lttng.scope.common.core,
+ org.lttng.scope.common.ui,
+ org.lttng.scope.tmf2.views.core,
+ org.eclipse.tracecompass.tmf.core,
+ org.eclipse.tracecompass.tmf.ui,
+ org.eclipse.core.resources
+Export-Package: org.lttng.scope.tmf2.views.ui.activator.internal;x-internal:=true,
+ org.lttng.scope.tmf2.views.ui.jfx,
+ org.lttng.scope.tmf2.views.ui.jfx.examples,
+ org.lttng.scope.tmf2.views.ui.timeline,
+ org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph,
+ org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.toolbar,
+ org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.toolbar.nav
+Import-Package: com.google.common.annotations,
+ com.google.common.base,
+ com.google.common.collect,
+ com.google.common.primitives
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"/>
+<title>About</title>
+</head>
+<body lang="EN-US">
+<h2>About This Content</h2>
+
+<p>June 5, 2006</p>
+<h3>License</h3>
+
+<p>The Eclipse Foundation makes available all content in this plug-in ("Content"). Unless otherwise
+indicated below, the Content is provided to you under the terms and conditions of the
+Eclipse Public License Version 1.0 ("EPL"). A copy of the EPL is available
+at <a href="http://www.eclipse.org/legal/epl-v10.html">http://www.eclipse.org/legal/epl-v10.html</a>.
+For purposes of the EPL, "Program" will mean the Content.</p>
+
+<p>If you did not receive this Content directly from the Eclipse Foundation, the Content is
+being redistributed by another party ("Redistributor") and different terms and conditions may
+apply to your use of any object code in the Content. Check the Redistributor's license that was
+provided with the Content. If no such license exists, contact the Redistributor. Unless otherwise
+indicated below, the terms and conditions of the EPL still apply to any source code in the Content
+and such source code may be obtained at <a href="http://www.eclipse.org/">http://www.eclipse.org</a>.</p>
+
+</body>
+</html>
\ No newline at end of file
--- /dev/null
+###############################################################################
+# Copyright (C) 2017 EfficiOS Inc.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Eclipse Public License v1.0
+# which accompanies this distribution, and is available at
+# http://www.eclipse.org/legal/epl-v10.html
+###############################################################################
+
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+ icons/,\
+ .,\
+ about.html,\
+ plugin.properties,\
+ plugin.xml
+src.includes = about.html
+additional.bundles = org.eclipse.jdt.annotation
+jars.extra.classpath = platform:/plugin/org.eclipse.jdt.annotation
--- /dev/null
+###############################################################################
+# Copyright (C) 2017 EfficiOS Inc.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Eclipse Public License v1.0
+# which accompanies this distribution, and is available at
+# http://www.eclipse.org/legal/epl-v10.html
+###############################################################################
+
+Bundle-Vendor = LTTng Scope
+Bundle-Name = LTTng Scope TMF2 Views UI Plug-in
+
+view.timeline.name = Timeline
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<?eclipse version="3.4"?>
+<plugin>
+ <extension
+ point="org.eclipse.ui.views">
+ <view
+ allowMultiple="true"
+ category="org.eclipse.linuxtools.tmf.ui.views.category"
+ class="org.lttng.scope.tmf2.views.ui.timeline.TimelineView"
+ id="org.lttng.scope.views.timeline"
+ name="%view.timeline.name"
+ restorable="true">
+ </view>
+ </extension>
+
+</plugin>
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.ui.activator.internal;
+
+import org.lttng.scope.common.ui.ScopeUIActivator;
+
+import javafx.application.Platform;
+
+/**
+ * The activator class controls the plug-in life cycle
+ *
+ * @noreference This class should not be accessed outside of this plugin.
+ */
+public class Activator extends ScopeUIActivator {
+
+ /**
+ * Return the singleton instance of this activator.
+ *
+ * @return The singleton instance
+ */
+ public static Activator instance() {
+ return ScopeUIActivator.getInstance(Activator.class);
+ }
+
+ @Override
+ protected void startActions() {
+ Platform.setImplicitExit(false);
+ }
+
+ @Override
+ protected void stopActions() {
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+@org.eclipse.jdt.annotation.NonNullByDefault
+package org.lttng.scope.tmf2.views.ui.activator.internal;
--- /dev/null
+/*
+ * This work is licensed under the Creative Commons Attribution-ShareAlike 3.0
+ * Unported License. To view a copy of this license, visit
+ * https://creativecommons.org/licenses/by-sa/3.0/.
+ */
+
+package org.lttng.scope.tmf2.views.ui.jfx;
+
+import org.eclipse.jdt.annotation.Nullable;
+
+import javafx.beans.InvalidationListener;
+import javafx.beans.property.DoubleProperty;
+import javafx.beans.property.ObjectProperty;
+import javafx.scene.Group;
+import javafx.scene.paint.Paint;
+import javafx.scene.shape.Line;
+
+/**
+ * JavaFX node representing a line with an arrow head at the end point.
+ *
+ * Initially inspired from http://stackoverflow.com/a/41353991/4227853.
+ */
+public class Arrow extends Group {
+
+ private static final double ARROW_HEAD_LENGTH = 5;
+ private static final double ARROW_HEAD_WIDTH = 3;
+
+ private final Line line;
+
+ /**
+ * Constructor specifying the initial coordinates of the arrow. The arrow
+ * head is drawn at the end point.
+ *
+ * @param startX
+ * X of start point
+ * @param startY
+ * Y of end point
+ * @param endX
+ * X of end point
+ * @param endY
+ * Y of end point.
+ */
+ public Arrow(double startX, double startY, double endX, double endY) {
+ this();
+ setStartX(startX);
+ setStartY(startY);
+ setEndX(endX);
+ setEndY(endY);
+ }
+
+ /**
+ * Constructor with default coordinates (usually (0,0)).
+ */
+ public Arrow() {
+ this(new Line(), new Line(), new Line());
+ }
+
+ private Arrow(Line line, Line arrow1, Line arrow2) {
+ super(line, arrow1, arrow2);
+ this.line = line;
+
+ /* The color of the arrow should follow the color of the main line. */
+ arrow1.strokeProperty().bind(line.strokeProperty());
+ arrow2.strokeProperty().bind(line.strokeProperty());
+
+ /* Listener to redraw the arrow parts when the line position changes. */
+ InvalidationListener updater = o -> {
+ double ex = getEndX();
+ double ey = getEndY();
+ double sx = getStartX();
+ double sy = getStartY();
+
+ arrow1.setEndX(ex);
+ arrow1.setEndY(ey);
+ arrow2.setEndX(ex);
+ arrow2.setEndY(ey);
+
+ if (ex == sx && ey == sy) {
+ /* The line is just a point. Don't draw the arrowhead. */
+ arrow1.setStartX(ex);
+ arrow1.setStartY(ey);
+ arrow2.setStartX(ex);
+ arrow2.setStartY(ey);
+ } else {
+ double factor = ARROW_HEAD_LENGTH / Math.hypot(sx-ex, sy-ey);
+ double factorO = ARROW_HEAD_WIDTH / Math.hypot(sx-ex, sy-ey);
+
+ // part in direction of main line
+ double dx = (sx - ex) * factor;
+ double dy = (sy - ey) * factor;
+
+ // part ortogonal to main line
+ double ox = (sx - ex) * factorO;
+ double oy = (sy - ey) * factorO;
+
+ arrow1.setStartX(ex + dx - oy);
+ arrow1.setStartY(ey + dy + ox);
+ arrow2.setStartX(ex + dx + oy);
+ arrow2.setStartY(ey + dy - ox);
+ }
+ };
+
+ /* Attach updater to properties */
+ startXProperty().addListener(updater);
+ startYProperty().addListener(updater);
+ endXProperty().addListener(updater);
+ endYProperty().addListener(updater);
+ updater.invalidated(null);
+ }
+
+ /**
+ * Set the value of the property startX.
+ *
+ * @param value
+ * The new value
+ */
+ public final void setStartX(double value) {
+ line.setStartX(value);
+ }
+
+ /**
+ * Get the value of the property startX.
+ *
+ * @return The current value
+ */
+ public final double getStartX() {
+ return line.getStartX();
+ }
+
+ /**
+ * The startX property
+ *
+ * @return The startX property
+ */
+ public final DoubleProperty startXProperty() {
+ return line.startXProperty();
+ }
+
+ /**
+ * Set the value of the property startY.
+ *
+ * @param value
+ * The new value
+ */
+ public final void setStartY(double value) {
+ line.setStartY(value);
+ }
+
+ /**
+ * Get the value of the property startY.
+ *
+ * @return The current value
+ */
+ public final double getStartY() {
+ return line.getStartY();
+ }
+
+ /**
+ * The startY property
+ *
+ * @return The startY property
+ */
+ public final DoubleProperty startYProperty() {
+ return line.startYProperty();
+ }
+
+ /**
+ * Set the value of the property endX.
+ *
+ * @param value
+ * The new value
+ */
+ public final void setEndX(double value) {
+ line.setEndX(value);
+ }
+
+ /**
+ * Get the value of the property endX.
+ *
+ * @return The current value
+ */
+ public final double getEndX() {
+ return line.getEndX();
+ }
+
+ /**
+ * The endX property
+ *
+ * @return The endX property
+ */
+ public final DoubleProperty endXProperty() {
+ return line.endXProperty();
+ }
+
+ /**
+ * Set the value of the property endY.
+ *
+ * @param value
+ * The new value
+ */
+ public final void setEndY(double value) {
+ line.setEndY(value);
+ }
+
+ /**
+ * Get the value of the property endY.
+ *
+ * @return The current value
+ */
+ public final double getEndY() {
+ return line.getEndY();
+ }
+
+ /**
+ * The endY property
+ *
+ * @return The endY property
+ */
+ public final DoubleProperty endYProperty() {
+ return line.endYProperty();
+ }
+
+ /**
+ * Set the value of the stroke property.
+ *
+ * @param value
+ * The new value
+ */
+ public final void setStroke(@Nullable Paint value) {
+ line.setStroke(value);
+ }
+
+ /**
+ * Get the value of the stroke property.
+ *
+ * @return The current value
+ */
+ public final @Nullable Paint getStroke() {
+ return line.getStroke();
+ }
+
+ /**
+ * The stroke property, which determines the lines' color.
+ *
+ * @return The stroke property
+ */
+ public final ObjectProperty<@Nullable Paint> strokeProperty() {
+ return line.strokeProperty();
+ }
+
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.ui.jfx;
+
+import javafx.scene.Node;
+import javafx.scene.layout.GridPane;
+
+/**
+ * Extension of a {@link GridPane} which tracks the number of inserted rows.
+ *
+ * Make sure you only add rows to the pane using {@link #appendRow(Node...)} or
+ * else it will give weird results!
+ *
+ * @author Alexandre Montplaisir
+ */
+public class CountingGridPane extends GridPane {
+
+ private int fNbRows = 0;
+
+ /**
+ * Add a row of nodes to this grid pane. Not thread-safe!
+ *
+ * @param children
+ * The contents of the row
+ */
+ public void appendRow(Node... children) {
+ addRow(fNbRows++, children);
+ }
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *******************************************************************************/
+
+package org.lttng.scope.tmf2.views.ui.jfx;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.ColorDefinition;
+
+import javafx.scene.paint.Color;
+
+public final class JfxColorFactory {
+
+ private JfxColorFactory() {}
+
+ private static final Map<ColorDefinition, Color> COLOR_MAP = new ConcurrentHashMap<>();
+ private static final Map<ColorDefinition, Color> DERIVED_COLOR_MAP = new ConcurrentHashMap<>();
+
+ /**
+ * Instantiate a {@link Color} from a {@link ColorDefinition} object.
+ *
+ * @param colorDef
+ * The ColorDefinition
+ * @return The Color object
+ */
+ public static synchronized Color getColorFromDef(ColorDefinition colorDef) {
+ Color color = COLOR_MAP.get(colorDef);
+ if (color == null) {
+ color = Color.rgb(colorDef.fRed, colorDef.fGreen, colorDef.fBlue, (double) colorDef.fAlpha / (double) ColorDefinition.MAX);
+ COLOR_MAP.put(colorDef, color);
+ }
+ return color;
+ }
+
+ public static synchronized Color getDerivedColorFromDef(ColorDefinition colorDef) {
+ Color color = DERIVED_COLOR_MAP.get(colorDef);
+ if (color == null) {
+ color = getColorFromDef(colorDef);
+ color = color.desaturate().darker();
+ DERIVED_COLOR_MAP.put(colorDef, color);
+ }
+ return color;
+ }
+
+ /**
+ * Convert a JavaFX {@link Color} to its equivalent {@link ColorDefinition}.
+ *
+ * @param color
+ * The color to convert
+ * @return A corresponding ColorDefinition
+ */
+ public static ColorDefinition colorToColorDef(Color color) {
+ /*
+ * ColorDefintion works with integer values 0 to 255, but JavaFX colors
+ * works with doubles 0.0 to 0.1
+ */
+ int red = (int) Math.round(color.getRed() * 255);
+ int green = (int) Math.round(color.getGreen() * 255);
+ int blue = (int) Math.round(color.getBlue() * 255);
+ int opacity = (int) Math.round(color.getOpacity() * 255);
+ return new ColorDefinition(red, green, blue, opacity);
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.ui.jfx;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.jdt.annotation.Nullable;
+
+import javafx.scene.image.Image;
+
+/**
+ * Factory for JavaFX {@link Image}s. This will allow caching the Image objects,
+ * allowing any class to re-use already read images.
+ *
+ * @author Alexandre Montplaisir
+ * @noreference This cache is only valid for classes within the same jar/plugin.
+ * The resource paths would not work from different plugins.
+ */
+public final class JfxImageFactory {
+
+ private static final JfxImageFactory INSTANCE = new JfxImageFactory();
+
+ private JfxImageFactory() {}
+
+ /**
+ * Get the singleton instance of this factory.
+ *
+ * @return The instance
+ */
+ public static JfxImageFactory instance() {
+ return INSTANCE;
+ }
+
+ private final Map<String, Image> fImages = new HashMap<>();
+
+ /**
+ * Get the {@link Image} for a given path within the jar's resources.
+ *
+ * @param resourcePath
+ * The path to the image resource. It should be a standard
+ * .gif/.png/.jpg etc. file.
+ * @return The corresponding Image.
+ */
+ public synchronized @Nullable Image getImageFromResource(String resourcePath) {
+ Image image = fImages.get(resourcePath);
+ if (image == null) {
+ try (InputStream is = getClass().getResourceAsStream(resourcePath)) {
+ if (is == null) {
+ /* The image was not found, the path is invalid */
+ return null;
+ }
+ image = new Image(is);
+ } catch (IOException e) {
+ return null;
+ }
+ fImages.put(resourcePath, image);
+ }
+ return image;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.ui.jfx;
+
+import static java.util.Objects.requireNonNull;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.Method;
+import java.util.List;
+
+import javafx.application.Platform;
+import javafx.beans.property.ReadOnlyDoubleProperty;
+import javafx.beans.property.SimpleDoubleProperty;
+import javafx.geometry.Rectangle2D;
+import javafx.scene.Node;
+import javafx.scene.control.Dialog;
+import javafx.scene.control.OverrunStyle;
+import javafx.scene.text.Font;
+import javafx.stage.Screen;
+import javafx.stage.Window;
+
+/**
+ * JavaFX-related utilities
+ *
+ * @author Alexandre Montplaisir
+ */
+public final class JfxUtils {
+
+ /**
+ * Double property with a non-modifiable value of 0. For things that should
+ * remain at 0.
+ */
+ public static final ReadOnlyDoubleProperty ZERO_PROPERTY = new SimpleDoubleProperty(0);
+
+
+ private static final MethodHandles.Lookup LOOKUP = requireNonNull(MethodHandles.lookup());
+ private static final MethodHandle COMPUTE_CLIPPED_TEXT_HANDLE;
+ static {
+ MethodHandle handle = null;
+ try {
+ Class<?> c = Class.forName("com.sun.javafx.scene.control.skin.Utils"); //$NON-NLS-1$
+ Method method = c.getDeclaredMethod("computeClippedText", //$NON-NLS-1$
+ Font.class, String.class, double.class, OverrunStyle.class, String.class);
+ method.setAccessible(true);
+ handle = LOOKUP.unreflect(method);
+ } catch (ClassNotFoundException | NoSuchMethodException | SecurityException | IllegalAccessException e) {
+ /* Should not fail if we did everything correctly. */
+ }
+ COMPUTE_CLIPPED_TEXT_HANDLE = requireNonNull(handle);
+ }
+
+ /**
+ * Accessor for the
+ * com.sun.javafx.scene.control.skin.Utils.computeClippedText() method.
+ *
+ * This method implements the logic to clip Label strings. It can be useful
+ * for other types, like Text. Unfortunately it is not public, but this
+ * accessor allows calling it through reflection. It makes use of
+ * {@link MethodHandle#invokeExact}, which should be close to just as fast
+ * as a standard compiled method call.
+ *
+ * @param font
+ * The font of the text that will be used
+ * @param text
+ * The string to clip
+ * @param width
+ * The maximum width we want to limit the string to
+ * @param type
+ * The {@link OverrunStyle}
+ * @param ellipsisString
+ * The string to use as ellipsis
+ * @return The clipped string, or "ERROR" if an error happened.
+ * Unfortunately we lose the exception typing due to the reflection
+ * call, so we do not want to throw "Throwable" here.
+ */
+ public static String computeClippedText(Font font, String text, double width,
+ OverrunStyle type, String ellipsisString) {
+ try {
+ String str = (String) COMPUTE_CLIPPED_TEXT_HANDLE.invokeExact(font, text, width, type, ellipsisString);
+ return requireNonNull(str);
+ } catch (Throwable e) {
+ return "ERROR"; //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * Run the given {@link Runnable} on the UI/main/application thread.
+ *
+ * If you know for sure you are *not* on the main thread, you should use
+ * {@link Platform#runLater} to queue the runnable for the main thread.
+ *
+ * If you are not sure, you can use this method. The difference with
+ * {@link Platform#runLater} is that if you are actually already on the UI
+ * thread, the runnable will be run immediately. Whereas calling runLater
+ * from the UI will just queue the runnable at the end of the queue.
+ *
+ * @param r
+ * The runnable to run on the main thread
+ */
+ public static void runOnMainThread(Runnable r) {
+ if (Platform.isFxApplicationThread()) {
+ r.run();
+ } else {
+ Platform.runLater(r);
+ }
+ }
+
+ /**
+ * Utility method to center a Dialog/Alert on the middle of the current
+ * screen. Used to workaround a "bug" with the current version of JavaFX (or
+ * the SWT/JavaFX embedding?) where alerts always show on the primary
+ * screen, not necessarily the current one.
+ *
+ * @param dialog
+ * The dialog to reposition. It must be already shown, or else
+ * this will do nothing.
+ * @param referenceNode
+ * The dialog should be moved to the same screen as this node's
+ * window.
+ */
+ public static void centerDialogOnScreen(Dialog<?> dialog, Node referenceNode) {
+ Window window = referenceNode.getScene().getWindow();
+ Rectangle2D windowRectangle = new Rectangle2D(window.getX(), window.getY(), window.getWidth(), window.getHeight());
+
+ List<Screen> screens = Screen.getScreensForRectangle(windowRectangle);
+ Screen screen = screens.stream()
+ .findFirst()
+ .orElse(Screen.getPrimary());
+
+ Rectangle2D screenBounds = screen.getBounds();
+ dialog.setX((screenBounds.getWidth() - dialog.getWidth()) / 2 + screenBounds.getMinX());
+// dialog.setY((screenBounds.getHeight() - dialog.getHeight()) / 2 + screenBounds.getMinY());
+ }
+}
--- /dev/null
+package org.lttng.scope.tmf2.views.ui.jfx;
+
+import org.eclipse.jdt.annotation.Nullable;
+
+import javafx.animation.Animation;
+import javafx.animation.Interpolator;
+import javafx.animation.RotateTransition;
+import javafx.application.Application;
+import javafx.scene.Group;
+import javafx.scene.Scene;
+import javafx.scene.layout.Pane;
+import javafx.scene.paint.Color;
+import javafx.scene.shape.Circle;
+import javafx.scene.shape.Rectangle;
+import javafx.stage.Stage;
+import javafx.util.Duration;
+
+public class Logo extends Application {
+
+ private static final String BACKGROUND_STYLE = "-fx-background-color: rgba(255, 255, 255, 255);"; //$NON-NLS-1$
+
+ private static final Color LTTNG_PURPLE = Color.web("#996ABC"); //$NON-NLS-1$
+ private static final Color LTTNG_LIGHT_BLUE = Color.web("#C3DEF4"); //$NON-NLS-1$
+
+ @Override
+ public void start(@Nullable Stage stage) throws Exception {
+ if (stage == null) {
+ return;
+ }
+
+ Rectangle clipRect1 = new Rectangle(-130, -130, 115, 115);
+ Rectangle clipRect2 = new Rectangle(15, -130, 115, 115);
+ Rectangle clipRect3 = new Rectangle(-130, 15, 115, 115);
+ Rectangle clipRect4 = new Rectangle(15, 15, 115, 115);
+ Group clip = new Group(clipRect1, clipRect2, clipRect3, clipRect4);
+
+ Circle spinnanCircle = new Circle(100);
+ spinnanCircle.setFill(null);
+ spinnanCircle.setStrokeWidth(30);
+ spinnanCircle.setStroke(LTTNG_PURPLE);
+ spinnanCircle.setClip(clip);
+
+ Circle magCircle = new Circle(60);
+ magCircle.setFill(null);
+ magCircle.setStrokeWidth(25);
+ magCircle.setStroke(LTTNG_LIGHT_BLUE);
+
+ Rectangle magHandle = new Rectangle(-12.5, 60, 25, 110);
+ magHandle.setFill(LTTNG_LIGHT_BLUE);
+
+ Group mag = new Group(magCircle, magHandle);
+
+ Group root = new Group(spinnanCircle, mag);
+ root.setRotate(30);
+ root.relocate(0, 0);
+
+ Pane pane = new Pane(root);
+ pane.setStyle(BACKGROUND_STYLE);
+
+ RotateTransition spinnan = new RotateTransition(Duration.seconds(4), spinnanCircle);
+ spinnan.setByAngle(360);
+ spinnan.setCycleCount(Animation.INDEFINITE);
+ spinnan.setInterpolator(Interpolator.LINEAR);
+
+ Scene scene = new Scene(pane);
+ stage.setScene(scene);
+ stage.show();
+
+ spinnan.play();
+ }
+
+ public static void main(String[] args) {
+ launch(args);
+ }
+
+}
--- /dev/null
+package org.lttng.scope.tmf2.views.ui.jfx.examples;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.lttng.scope.tmf2.views.ui.jfx.Arrow;
+
+import javafx.application.Application;
+import javafx.scene.Scene;
+import javafx.scene.layout.Pane;
+import javafx.scene.paint.Color;
+import javafx.stage.Stage;
+
+public class ArrowExample extends Application {
+
+ public static void main(String[] args) {
+ launch(args);
+ }
+
+ @Override
+ public void start(@Nullable Stage primaryStage) throws Exception {
+ if (primaryStage == null) {
+ return;
+ }
+
+ Pane root = new Pane();
+ Arrow arrow = new Arrow();
+ arrow.setStroke(Color.GREEN);
+ root.getChildren().add(arrow);
+
+ root.setOnMouseClicked(evt -> {
+ switch (evt.getButton()) {
+ case PRIMARY:
+ // set pos of end with arrow head
+ arrow.setEndX(evt.getX());
+ arrow.setEndY(evt.getY());
+ break;
+ case SECONDARY:
+ // set pos of end without arrow head
+ arrow.setStartX(evt.getX());
+ arrow.setStartY(evt.getY());
+ break;
+ case MIDDLE:
+ case NONE:
+ default:
+ break;
+ }
+ });
+
+ Scene scene = new Scene(root, 400, 400);
+
+ primaryStage.setScene(scene);
+ primaryStage.show();
+ }
+
+}
--- /dev/null
+package org.lttng.scope.tmf2.views.ui.jfx.examples;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+import javafx.application.Application;
+import javafx.beans.value.ChangeListener;
+import javafx.beans.value.ObservableValue;
+import javafx.scene.Node;
+import javafx.scene.Scene;
+import javafx.scene.control.ScrollPane;
+import javafx.scene.paint.Color;
+import javafx.scene.shape.Rectangle;
+import javafx.stage.Stage;
+
+@NonNullByDefault({})
+public class Example extends Application {
+ @Override
+ public void start(Stage stage) {
+ Node content = new Rectangle(1000, 700, Color.GREEN);
+ ScrollPane scrollPane = new ScrollPane(content);
+ scrollPane.setPrefSize(500, 300);
+
+ ChangeListener<Object> changeListener = new ChangeListener<Object>() {
+ @Override
+ public void changed(ObservableValue<? extends Object> observable, Object oldValue, Object newValue) {
+ double hmin = scrollPane.getHmin();
+ double hmax = scrollPane.getHmax();
+ double hvalue = scrollPane.getHvalue();
+ double contentWidth = content.getLayoutBounds().getWidth();
+ double viewportWidth = scrollPane.getViewportBounds().getWidth();
+
+ double hoffset =
+ Math.max(0, contentWidth - viewportWidth) * (hvalue - hmin) / (hmax - hmin);
+
+ double vmin = scrollPane.getVmin();
+ double vmax = scrollPane.getVmax();
+ double vvalue = scrollPane.getVvalue();
+ double contentHeight = content.getLayoutBounds().getHeight();
+ double viewportHeight = scrollPane.getViewportBounds().getHeight();
+
+ double voffset =
+ Math.max(0, contentHeight - viewportHeight) * (vvalue - vmin) / (vmax - vmin);
+
+ System.out.printf("Offset: [%.1f, %.1f] width: %.1f height: %.1f %n",
+ hoffset, voffset, viewportWidth, viewportHeight);
+ }
+ };
+ scrollPane.viewportBoundsProperty().addListener(changeListener);
+ scrollPane.hvalueProperty().addListener(changeListener);
+ scrollPane.vvalueProperty().addListener(changeListener);
+
+ Scene scene = new Scene(scrollPane, 640, 480);
+ stage.setScene(scene);
+
+ stage.show();
+ }
+
+ public static void main(String[] args) {
+ launch(args);
+ }
+}
\ No newline at end of file
--- /dev/null
+package org.lttng.scope.tmf2.views.ui.jfx.examples;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+import javafx.application.Application;
+import javafx.beans.value.ChangeListener;
+import javafx.beans.value.ObservableValue;
+import javafx.scene.Scene;
+import javafx.scene.canvas.Canvas;
+import javafx.scene.control.ScrollPane;
+import javafx.scene.layout.Pane;
+import javafx.stage.Stage;
+
+@NonNullByDefault({})
+public class Example2 extends Application {
+
+ private static final double TEN_BILLIONS = 10000000000.0;
+
+ @Override
+ public void start(Stage stage) {
+// Node content = new Rectangle(1000, 700, Color.GREEN);
+ Pane pane = new Pane();
+ pane.setPrefSize(TEN_BILLIONS, TEN_BILLIONS);
+ ScrollPane scrollPane = new ScrollPane(pane);
+ scrollPane.setPrefSize(500, 300);
+
+ ChangeListener<Object> changeListener = new ChangeListener<Object>() {
+ @Override
+ public void changed(ObservableValue<? extends Object> observable, Object oldValue, Object newValue) {
+ System.out.println("source=" + observable.toString());
+
+ double hmin = scrollPane.getHmin();
+ double hmax = scrollPane.getHmax();
+ double hvalue = scrollPane.getHvalue();
+ double contentWidth = pane.getLayoutBounds().getWidth();
+ double viewportWidth = scrollPane.getViewportBounds().getWidth();
+
+ double hoffset =
+ Math.max(0, contentWidth - viewportWidth) * (hvalue - hmin) / (hmax - hmin);
+
+ double vmin = scrollPane.getVmin();
+ double vmax = scrollPane.getVmax();
+ double vvalue = scrollPane.getVvalue();
+ double contentHeight = pane.getLayoutBounds().getHeight();
+ double viewportHeight = scrollPane.getViewportBounds().getHeight();
+
+ double voffset =
+ Math.max(0, contentHeight - viewportHeight) * (vvalue - vmin) / (vmax - vmin);
+
+ System.out.printf("Offset: [%.1f, %.1f] width: %.1f height: %.1f %n",
+ hoffset, voffset, viewportWidth, viewportHeight);
+ }
+ };
+ scrollPane.viewportBoundsProperty().addListener(changeListener);
+ scrollPane.hvalueProperty().addListener(changeListener);
+ scrollPane.vvalueProperty().addListener(changeListener);
+
+ /* Drawing on the region */
+ Canvas canvas1 = new Canvas(100, 100);
+ canvas1.relocate(TEN_BILLIONS - 100, 0);
+ canvas1.getGraphicsContext2D().strokeOval(60, 60, 30, 30);
+
+ Canvas canvas2 = new Canvas(100, 100);
+ canvas2.relocate(TEN_BILLIONS - 100, TEN_BILLIONS - 100);
+ canvas2.getGraphicsContext2D().fillOval(60, 60, 30, 30);
+
+ pane.getChildren().addAll(canvas1, canvas2);
+
+ /* Showing the scene */
+ Scene scene = new Scene(scrollPane, 640, 480);
+ stage.setScene(scene);
+
+ stage.show();
+ }
+
+ public static void main(String[] args) {
+ launch(args);
+ }
+}
\ No newline at end of file
--- /dev/null
+package org.lttng.scope.tmf2.views.ui.jfx.examples;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+import javafx.application.Application;
+import javafx.scene.Group;
+import javafx.scene.Scene;
+import javafx.scene.canvas.Canvas;
+import javafx.scene.canvas.GraphicsContext;
+import javafx.scene.paint.Color;
+import javafx.scene.shape.ArcType;
+import javafx.stage.Stage;
+
+@NonNullByDefault({})
+public class ExampleCanvas extends Application {
+
+ public static void main(String[] args) {
+ launch(args);
+ }
+
+ @Override
+ public void start(Stage primaryStage) {
+ primaryStage.setTitle("Drawing Operations Test");
+ Group root = new Group();
+ Canvas canvas = new Canvas(300, 250);
+ GraphicsContext gc = canvas.getGraphicsContext2D();
+ drawShapes(gc);
+ root.getChildren().add(canvas);
+ primaryStage.setScene(new Scene(root));
+ primaryStage.show();
+ }
+
+ private static void drawShapes(GraphicsContext gc) {
+ gc.setFill(Color.GREEN);
+ gc.setStroke(Color.BLUE);
+ gc.setLineWidth(5);
+ gc.strokeLine(40, 10, 10, 40);
+ gc.fillOval(10, 60, 30, 30);
+ gc.strokeOval(60, 60, 30, 30);
+ gc.fillRoundRect(110, 60, 30, 30, 10, 10);
+ gc.strokeRoundRect(160, 60, 30, 30, 10, 10);
+ gc.fillArc(10, 110, 30, 30, 45, 240, ArcType.OPEN);
+ gc.fillArc(60, 110, 30, 30, 45, 240, ArcType.CHORD);
+ gc.fillArc(110, 110, 30, 30, 45, 240, ArcType.ROUND);
+ gc.strokeArc(10, 160, 30, 30, 45, 240, ArcType.OPEN);
+ gc.strokeArc(60, 160, 30, 30, 45, 240, ArcType.CHORD);
+ gc.strokeArc(110, 160, 30, 30, 45, 240, ArcType.ROUND);
+ gc.fillPolygon(new double[]{10, 40, 10, 40},
+ new double[]{210, 210, 240, 240}, 4);
+ gc.strokePolygon(new double[]{60, 90, 60, 90},
+ new double[]{210, 210, 240, 240}, 4);
+ gc.strokePolyline(new double[]{110, 140, 110, 140},
+ new double[]{210, 210, 240, 240}, 4);
+ }
+}
\ No newline at end of file
--- /dev/null
+package org.lttng.scope.tmf2.views.ui.jfx.examples;
+
+import org.eclipse.jdt.annotation.Nullable;
+
+import javafx.application.Application;
+import javafx.event.EventHandler;
+import javafx.scene.Cursor;
+import javafx.scene.Group;
+import javafx.scene.Scene;
+import javafx.scene.input.MouseEvent;
+import javafx.scene.paint.Color;
+import javafx.scene.shape.Circle;
+import javafx.stage.Stage;
+
+/**
+ * @web http://java-buddy.blogspot.com/
+ */
+public class ExampleMouseDrag extends Application {
+
+ private double orgSceneX, orgSceneY;
+ private double orgTranslateX, orgTranslateY;
+
+ @Override
+ public void start(@Nullable Stage primaryStage) {
+ if (primaryStage == null) {
+ return;
+ }
+
+ //Create Circles
+ Circle circleRed = new Circle(50.0, Color.RED);
+ circleRed.setCursor(Cursor.HAND);
+ circleRed.setOnMousePressed(circleOnMousePressedEventHandler);
+ circleRed.setOnMouseDragged(circleOnMouseDraggedEventHandler);
+
+ Circle circleGreen = new Circle(50.0, Color.GREEN);
+ circleGreen.setCursor(Cursor.MOVE);
+ circleGreen.setCenterX(150);
+ circleGreen.setCenterY(150);
+ circleGreen.setOnMousePressed(circleOnMousePressedEventHandler);
+ circleGreen.setOnMouseDragged(circleOnMouseDraggedEventHandler);
+
+ Circle circleBlue = new Circle(50.0, Color.BLUE);
+ circleBlue.setCursor(Cursor.CROSSHAIR);
+ circleBlue.setTranslateX(300);
+ circleBlue.setTranslateY(100);
+ circleBlue.setOnMousePressed(circleOnMousePressedEventHandler);
+ circleBlue.setOnMouseDragged(circleOnMouseDraggedEventHandler);
+
+ Group root = new Group();
+ root.getChildren().addAll(circleRed, circleGreen, circleBlue);
+
+ primaryStage.setResizable(false);
+ primaryStage.setScene(new Scene(root, 400,350));
+
+ primaryStage.setTitle("java-buddy");
+ primaryStage.show();
+ }
+
+ public static void main(String[] args) {
+ launch(args);
+ }
+
+ private EventHandler<MouseEvent> circleOnMousePressedEventHandler = new EventHandler<MouseEvent>() {
+ @Override
+ public void handle(MouseEvent t) {
+ orgSceneX = t.getSceneX();
+ orgSceneY = t.getSceneY();
+ orgTranslateX = ((Circle)(t.getSource())).getTranslateX();
+ orgTranslateY = ((Circle)(t.getSource())).getTranslateY();
+ }
+ };
+
+ private EventHandler<MouseEvent> circleOnMouseDraggedEventHandler = new EventHandler<MouseEvent>() {
+ @Override
+ public void handle(MouseEvent t) {
+ double offsetX = t.getSceneX() - orgSceneX;
+ double offsetY = t.getSceneY() - orgSceneY;
+ double newTranslateX = orgTranslateX + offsetX;
+ double newTranslateY = orgTranslateY + offsetY;
+
+ ((Circle)(t.getSource())).setTranslateX(newTranslateX);
+ ((Circle)(t.getSource())).setTranslateY(newTranslateY);
+ }
+ };
+}
\ No newline at end of file
--- /dev/null
+package org.lttng.scope.tmf2.views.ui.jfx.examples;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Random;
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.Nullable;
+
+import javafx.application.Application;
+import javafx.event.EventHandler;
+import javafx.scene.Node;
+import javafx.scene.Scene;
+import javafx.scene.control.Label;
+import javafx.scene.image.Image;
+import javafx.scene.image.ImageView;
+import javafx.scene.input.MouseEvent;
+import javafx.scene.layout.Pane;
+import javafx.scene.layout.Region;
+import javafx.scene.paint.Color;
+import javafx.scene.shape.Rectangle;
+import javafx.scene.shape.StrokeLineCap;
+import javafx.stage.Stage;
+
+public class ExampleMouseDrag2 extends Application {
+
+// public static Image image = new Image("http://upload.wikimedia.org/wikipedia/commons/thumb/4/41/Siberischer_tiger_de_edit02.jpg/320px-Siberischer_tiger_de_edit02.jpg");
+ public Image image = new Image("file:///home/alexandre/Desktop/tiger.jpg");
+// public Image image = new Image( getClass().getResource( "/home/alexandre/Desktop/tiger.jpg").toExternalForm());
+
+ SelectionModel selectionModel = new SelectionModel();
+
+ DragMouseGestures dragMouseGestures = new DragMouseGestures();
+
+ static Random rnd = new Random();
+
+ @Override
+ public void start(@Nullable Stage primaryStage) {
+ if (primaryStage == null) {
+ return;
+ }
+
+ Pane pane = new Pane();
+ pane.setStyle("-fx-background-color:white");
+
+ new RubberBandSelection( pane);
+
+ double width = 200;
+ double height = 160;
+
+ double padding = 20;
+ for( int row=0; row < 4; row++) {
+ for( int col=0; col < 4; col++) {
+
+ Selectable selectable = new Selectable( width, height);
+ selectable.relocate( padding * (col+1) + width * col, padding * (row + 1) + height * row);
+
+ pane.getChildren().add(selectable);
+
+ dragMouseGestures.makeDraggable(selectable);
+
+ }
+ }
+
+ Label infoLabel = new Label( "Drag on scene for Rubberband Selection. Shift+Click to add to selection, CTRL+Click to toggle selection. Drag selected nodes for multi-dragging.");
+ pane.getChildren().add( infoLabel);
+
+ Scene scene = new Scene( pane, 1600, 900);
+ scene.getStylesheets().add( getClass().getResource("application.css").toExternalForm());
+
+ primaryStage.setScene( scene);
+ primaryStage.show();
+
+
+
+ }
+
+ private class Selectable extends Region {
+
+ ImageView view;
+
+ public Selectable( double width, double height) {
+
+ view = new ImageView( image);
+ view.setFitWidth(width);
+ view.setFitHeight(height);
+
+ getChildren().add( view);
+
+ this.setPrefSize(width, height);
+ }
+
+ }
+
+ private class SelectionModel {
+
+ Set<Node> selection = new HashSet<>();
+
+ public void add( Node node) {
+
+ if( !node.getStyleClass().contains("highlight")) {
+ node.getStyleClass().add( "highlight");
+ }
+
+ selection.add( node);
+ }
+
+ public void remove( Node node) {
+ node.getStyleClass().remove( "highlight");
+ selection.remove( node);
+ }
+
+ public void clear() {
+
+ while( !selection.isEmpty()) {
+ remove( selection.iterator().next());
+ }
+
+ }
+
+ public boolean contains( Node node) {
+ return selection.contains(node);
+ }
+
+// public int size() {
+// return selection.size();
+// }
+
+ public void log() {
+ System.out.println( "Items in model: " + Arrays.asList( selection.toArray()));
+ }
+
+ }
+
+ private class DragMouseGestures {
+
+ final DragContext dragContext = new DragContext();
+
+ private boolean enabled = false;
+
+ public void makeDraggable(final Node node) {
+
+ node.setOnMousePressed(onMousePressedEventHandler);
+ node.setOnMouseDragged(onMouseDraggedEventHandler);
+ node.setOnMouseReleased(onMouseReleasedEventHandler);
+
+ }
+
+ EventHandler<MouseEvent> onMousePressedEventHandler = new EventHandler<MouseEvent>() {
+
+ @Override
+ public void handle(MouseEvent event) {
+
+ // don't do anything if the user is in the process of adding to the selection model
+ if( event.isControlDown() || event.isShiftDown()) {
+ return;
+ }
+
+ Node node = (Node) event.getSource();
+
+ dragContext.x = node.getTranslateX() - event.getSceneX();
+ dragContext.y = node.getTranslateY() - event.getSceneY();
+
+ // clear the model if the current node isn't in the selection => new selection
+ if( !selectionModel.contains(node)) {
+ selectionModel.clear();
+ selectionModel.add( node);
+ }
+
+ // flag that the mouse released handler should consume the event, so it won't bubble up to the pane which has a rubberband selection mouse released handler
+ enabled = true;
+
+ // prevent rubberband selection handler
+ event.consume();
+ }
+ };
+
+ EventHandler<MouseEvent> onMouseDraggedEventHandler = new EventHandler<MouseEvent>() {
+
+ @Override
+ public void handle(MouseEvent event) {
+
+ if( !enabled) {
+ return;
+ }
+
+ // all in selection
+ for( Node node: selectionModel.selection) {
+ node.setTranslateX( dragContext.x + event.getSceneX());
+ node.setTranslateY( dragContext.y + event.getSceneY());
+ }
+
+ }
+ };
+
+ EventHandler<MouseEvent> onMouseReleasedEventHandler = new EventHandler<MouseEvent>() {
+
+ @Override
+ public void handle(MouseEvent event) {
+
+ // prevent rubberband selection handler
+ if( enabled) {
+
+ // set node's layout position to current position,remove translate coordinates
+ for( Node node: selectionModel.selection) {
+ fixPosition(node);
+ }
+
+ enabled = false;
+
+ event.consume();
+ }
+ }
+ };
+
+ /**
+ * Set node's layout position to current position, remove translate coordinates.
+ * @param node
+ */
+ private void fixPosition( Node node) {
+
+ double x = node.getTranslateX();
+ double y = node.getTranslateY();
+
+ node.relocate(node.getLayoutX() + x, node.getLayoutY() + y);
+
+ node.setTranslateX(0);
+ node.setTranslateY(0);
+
+ }
+
+ class DragContext {
+
+ double x;
+ double y;
+
+ }
+
+ }
+
+ private class RubberBandSelection {
+
+ final DragContext dragContext = new DragContext();
+ final Rectangle rect;
+
+ Pane group;
+ boolean enabled = false;
+
+ public RubberBandSelection( Pane group) {
+
+ this.group = group;
+
+ rect = new Rectangle( 0,0,0,0);
+ rect.setStroke(Color.BLUE);
+ rect.setStrokeWidth(1);
+ rect.setStrokeLineCap(StrokeLineCap.ROUND);
+ rect.setFill(Color.LIGHTBLUE.deriveColor(0, 1.2, 1, 0.6));
+
+ group.addEventHandler(MouseEvent.MOUSE_PRESSED, onMousePressedEventHandler);
+ group.addEventHandler(MouseEvent.MOUSE_DRAGGED, onMouseDraggedEventHandler);
+ group.addEventHandler(MouseEvent.MOUSE_RELEASED, onMouseReleasedEventHandler);
+
+ }
+
+ EventHandler<MouseEvent> onMousePressedEventHandler = new EventHandler<MouseEvent>() {
+
+ @Override
+ public void handle(MouseEvent event) {
+
+ // simple flag to prevent multiple handling of this event or we'd get an exception because rect is already on the scene
+ // eg if you drag with left mouse button and while doing that click the right mouse button
+ if( enabled) {
+ return;
+ }
+
+ dragContext.mouseAnchorX = event.getSceneX();
+ dragContext.mouseAnchorY = event.getSceneY();
+
+ rect.setX(dragContext.mouseAnchorX);
+ rect.setY(dragContext.mouseAnchorY);
+ rect.setWidth(0);
+ rect.setHeight(0);
+
+ group.getChildren().add( rect);
+
+ event.consume();
+
+ enabled = true;
+ }
+ };
+
+ EventHandler<MouseEvent> onMouseReleasedEventHandler = new EventHandler<MouseEvent>() {
+
+ @Override
+ public void handle(MouseEvent event) {
+
+ if( !event.isShiftDown() && !event.isControlDown()) {
+ selectionModel.clear();
+ }
+
+ for( Node node: group.getChildren()) {
+
+ if( node instanceof Selectable) {
+ if( node.getBoundsInParent().intersects( rect.getBoundsInParent())) {
+
+ if( event.isShiftDown()) {
+
+ selectionModel.add( node);
+
+ } else if( event.isControlDown()) {
+
+ if( selectionModel.contains( node)) {
+ selectionModel.remove( node);
+ } else {
+ selectionModel.add( node);
+ }
+ } else {
+ selectionModel.add( node);
+ }
+
+ }
+ }
+
+ }
+
+ selectionModel.log();
+
+ rect.setX(0);
+ rect.setY(0);
+ rect.setWidth(0);
+ rect.setHeight(0);
+
+ group.getChildren().remove( rect);
+
+ event.consume();
+
+ enabled = false;
+ }
+ };
+
+ EventHandler<MouseEvent> onMouseDraggedEventHandler = new EventHandler<MouseEvent>() {
+
+ @Override
+ public void handle(MouseEvent event) {
+
+ double offsetX = event.getSceneX() - dragContext.mouseAnchorX;
+ double offsetY = event.getSceneY() - dragContext.mouseAnchorY;
+
+ if( offsetX > 0) {
+ rect.setWidth( offsetX);
+ } else {
+ rect.setX(event.getSceneX());
+ rect.setWidth(dragContext.mouseAnchorX - rect.getX());
+ }
+
+ if( offsetY > 0) {
+ rect.setHeight( offsetY);
+ } else {
+ rect.setY(event.getSceneY());
+ rect.setHeight(dragContext.mouseAnchorY - rect.getY());
+ }
+
+ event.consume();
+
+ }
+ };
+
+ private final class DragContext {
+
+ public double mouseAnchorX;
+ public double mouseAnchorY;
+
+
+ }
+ }
+
+
+ public static void main(String[] args) {
+ launch(args);
+ }
+
+}
--- /dev/null
+package org.lttng.scope.tmf2.views.ui.jfx.examples;
+
+import org.eclipse.jdt.annotation.Nullable;
+
+import javafx.animation.FadeTransition;
+import javafx.application.Application;
+import javafx.scene.Group;
+import javafx.scene.Scene;
+import javafx.scene.shape.Rectangle;
+import javafx.stage.Stage;
+import javafx.util.Duration;
+
+public class FadeTransitionEx extends Application {
+
+ public static void main(String[] args) {
+ Application.launch(args);
+ }
+
+ @Override
+ public void start(@Nullable Stage primaryStage) {
+ if (primaryStage == null) {
+ return;
+ }
+
+ Group group = new Group();
+ Rectangle rect = new Rectangle(20,20,200,200);
+
+ FadeTransition ft = new FadeTransition(Duration.millis(5000), rect);
+ ft.setFromValue(1.0);
+ ft.setToValue(0.0);
+ ft.play();
+
+ group.getChildren().add(rect);
+
+ Scene scene = new Scene(group, 300, 200);
+ primaryStage.setScene(scene);
+ primaryStage.show();
+ }
+}
--- /dev/null
+package org.lttng.scope.tmf2.views.ui.jfx.examples;
+
+import org.eclipse.jdt.annotation.Nullable;
+
+import javafx.animation.Animation;
+import javafx.animation.FadeTransition;
+import javafx.animation.ParallelTransition;
+import javafx.animation.RotateTransition;
+import javafx.animation.ScaleTransition;
+import javafx.animation.TranslateTransition;
+import javafx.application.Application;
+import javafx.scene.Group;
+import javafx.scene.Scene;
+import javafx.scene.shape.Rectangle;
+import javafx.stage.Stage;
+import javafx.util.Duration;
+
+public class ParallelTransitionExample extends Application {
+
+ public static void main(String[] args) {
+ Application.launch(args);
+ }
+
+ @Override
+ public void start(@Nullable Stage primaryStage) {
+ if (primaryStage == null) {
+ return;
+ }
+
+ Group group = new Group();
+ Rectangle rectParallel = new Rectangle(20, 20, 200, 200);
+
+ FadeTransition fadeTransition = new FadeTransition(Duration.millis(3000),
+ rectParallel);
+ fadeTransition.setFromValue(1.0f);
+ fadeTransition.setToValue(0.3f);
+ fadeTransition.setCycleCount(2);
+ fadeTransition.setAutoReverse(true);
+
+ TranslateTransition translateTransition = new TranslateTransition(
+ Duration.millis(2000), rectParallel);
+ translateTransition.setFromX(50);
+ translateTransition.setToX(350);
+ translateTransition.setCycleCount(2);
+ translateTransition.setAutoReverse(true);
+
+ RotateTransition rotateTransition = new RotateTransition(
+ Duration.millis(3000), rectParallel);
+ rotateTransition.setByAngle(180f);
+ rotateTransition.setCycleCount(4);
+ rotateTransition.setAutoReverse(true);
+
+ ScaleTransition scaleTransition = new ScaleTransition(
+ Duration.millis(2000), rectParallel);
+ scaleTransition.setToX(2f);
+ scaleTransition.setToY(2f);
+ scaleTransition.setCycleCount(2);
+ scaleTransition.setAutoReverse(true);
+
+ ParallelTransition parallelTransition = new ParallelTransition();
+ parallelTransition.getChildren().addAll(fadeTransition,
+ translateTransition,
+ rotateTransition,
+ scaleTransition);
+ parallelTransition.setCycleCount(Animation.INDEFINITE);
+ parallelTransition.play();
+
+ group.getChildren().add(rectParallel);
+
+ Scene scene = new Scene(group, 300, 200);
+ primaryStage.setScene(scene);
+ primaryStage.show();
+ }
+}
--- /dev/null
+package org.lttng.scope.tmf2.views.ui.jfx.examples;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.layout.FillLayout;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Menu;
+import org.eclipse.swt.widgets.MenuItem;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.ToolBar;
+import org.eclipse.swt.widgets.ToolItem;
+
+@NonNullByDefault({})
+public class SwtToobar2 {
+
+ private Shell shell;
+
+ public SwtToobar2() {
+ Display display = new Display();
+ shell = new Shell(display, SWT.SHELL_TRIM);
+ shell.setLayout(new FillLayout(SWT.VERTICAL));
+ shell.setSize(50, 100);
+
+ ToolBar toolbar = new ToolBar(shell, SWT.FLAT);
+ ToolItem itemDrop = new ToolItem(toolbar, SWT.DROP_DOWN);
+ itemDrop.setText("drop menu");
+
+ itemDrop.addSelectionListener(new SelectionAdapter() {
+
+ Menu dropMenu = null;
+
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ if (dropMenu == null) {
+ dropMenu = new Menu(shell, SWT.POP_UP);
+ shell.setMenu(dropMenu);
+ MenuItem itemCheck = new MenuItem(dropMenu, SWT.CHECK);
+ itemCheck.setText("checkbox");
+ MenuItem itemRadio = new MenuItem(dropMenu, SWT.RADIO);
+ itemRadio.setText("radio1");
+ MenuItem itemRadio2 = new MenuItem(dropMenu, SWT.RADIO);
+ itemRadio2.setText("radio2");
+ }
+
+ if (e.detail == SWT.ARROW) {
+ // Position the menu below and vertically aligned with the
+ // the drop down tool button.
+ final ToolItem toolItem = (ToolItem) e.widget;
+ final ToolBar toolBar = toolItem.getParent();
+
+ Point point = toolBar.toDisplay(new Point(e.x, e.y));
+ dropMenu.setLocation(point.x, point.y);
+ dropMenu.setVisible(true);
+ }
+
+ }
+
+ });
+
+ shell.open();
+
+ while (!shell.isDisposed()) {
+ if (!display.readAndDispatch()) {
+ display.sleep();
+ }
+ }
+
+ display.dispose();
+ }
+
+ public static void main(String[] args) {
+ new SwtToobar2();
+ }
+
+}
\ No newline at end of file
--- /dev/null
+package org.lttng.scope.tmf2.views.ui.jfx.examples;
+
+/*******************************************************************************
+ * Copyright (c) 2000, 2016 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+/*
+ * ToolBar example snippet: place a drop down menu in a tool bar
+ *
+ * For a list of all SWT example snippets see
+ * http://www.eclipse.org/swt/snippets/
+ */
+import org.eclipse.swt.*;
+import org.eclipse.swt.graphics.*;
+import org.eclipse.swt.widgets.*;
+
+public class TestSwtToolbar {
+
+ public static void main(String[] args) {
+ final Display display = new Display();
+ final Shell shell = new Shell(display);
+
+ final ToolBar toolBar = new ToolBar(shell, SWT.NONE);
+ Rectangle clientArea = shell.getClientArea();
+ toolBar.setLocation(clientArea.x, clientArea.y);
+
+ final Menu menu = new Menu(shell, SWT.POP_UP);
+ for (int i = 0; i < 8; i++) {
+ MenuItem item = new MenuItem(menu, SWT.PUSH);
+ item.setText("Item " + i);
+ }
+
+ final ToolItem item = new ToolItem(toolBar, SWT.DROP_DOWN);
+ item.addListener(SWT.Selection, event -> {
+ if (event.detail == SWT.ARROW) {
+ Rectangle rect = item.getBounds();
+ Point pt = new Point(rect.x, rect.y + rect.height);
+ pt = toolBar.toDisplay(pt);
+ menu.setLocation(pt.x, pt.y);
+ menu.setVisible(true);
+ }
+ });
+
+ toolBar.pack();
+ shell.pack();
+ shell.open();
+ while (!shell.isDisposed()) {
+ if (!display.readAndDispatch()) {
+ display.sleep();
+ }
+ }
+ menu.dispose();
+ display.dispose();
+ }
+}
\ No newline at end of file
--- /dev/null
+package org.lttng.scope.tmf2.views.ui.jfx.examples;
+
+import java.net.MalformedURLException;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+import javafx.application.Application;
+import javafx.geometry.Bounds;
+import javafx.geometry.Point2D;
+import javafx.geometry.Pos;
+import javafx.scene.Group;
+import javafx.scene.Node;
+import javafx.scene.Scene;
+import javafx.scene.canvas.Canvas;
+import javafx.scene.canvas.GraphicsContext;
+import javafx.scene.control.Label;
+import javafx.scene.control.ScrollPane;
+import javafx.scene.control.SplitPane;
+import javafx.scene.layout.Pane;
+import javafx.scene.layout.Region;
+import javafx.scene.layout.StackPane;
+import javafx.scene.paint.Color;
+import javafx.scene.shape.Circle;
+import javafx.stage.Stage;
+
+@NonNullByDefault({})
+public class ZoomExample extends Application {
+
+ public static Region createContent() {
+ double width = 1000;
+ double height = 1000;
+
+ Canvas canvas = new Canvas(width, height);
+ GraphicsContext gc = canvas.getGraphicsContext2D();
+
+ gc.setFill(Color.LIGHTGREY);
+ gc.fillRect(0, 0, width, height);
+
+ gc.setStroke(Color.BLUE);
+ gc.beginPath();
+
+ for (int i = 50; i < width; i += 50) {
+ gc.moveTo(i, 0);
+ gc.lineTo(i, height);
+ }
+
+ for (int i = 50; i < height; i += 50) {
+ gc.moveTo(0, i);
+ gc.lineTo(width, i);
+ }
+ gc.stroke();
+
+ Pane content = new Pane(
+ new Circle(50, 50, 20),
+ new Circle(120, 90, 20, Color.RED),
+ new Circle(200, 70, 20, Color.GREEN)
+ );
+
+ StackPane result = new StackPane(canvas, content);
+ result.setAlignment(Pos.TOP_LEFT);
+
+ class DragData {
+
+ double startX;
+ double startY;
+ double startLayoutX;
+ double startLayoutY;
+ Node dragTarget;
+ }
+
+ DragData dragData = new DragData();
+
+ content.setOnMousePressed(evt -> {
+ if (evt.getTarget() != content) {
+ // initiate drag gesture, if a child of content receives the
+ // event to prevent ScrollPane from panning.
+ evt.consume();
+ evt.setDragDetect(true);
+ }
+ });
+
+ content.setOnDragDetected(evt -> {
+ Node n = (Node) evt.getTarget();
+ if (n != content) {
+ // set start paremeters
+ while (n.getParent() != content) {
+ n = n.getParent();
+ }
+ dragData.startX = evt.getX();
+ dragData.startY = evt.getY();
+ dragData.startLayoutX = n.getLayoutX();
+ dragData.startLayoutY = n.getLayoutY();
+ dragData.dragTarget = n;
+ n.startFullDrag();
+ evt.consume();
+ }
+ });
+
+ // stop dragging when mouse is released
+ content.setOnMouseReleased(evt -> dragData.dragTarget = null);
+
+ content.setOnMouseDragged(evt -> {
+ if (dragData.dragTarget != null) {
+ // move dragged node
+ dragData.dragTarget.setLayoutX(evt.getX() + dragData.startLayoutX - dragData.startX);
+ dragData.dragTarget.setLayoutY(evt.getY() + dragData.startLayoutY - dragData.startY);
+// Point2D p = new Point2D(evt.getX(), evt.getY());
+ evt.consume();
+ }
+ });
+
+ return result;
+ }
+
+ @Override
+ public void start(Stage primaryStage) throws MalformedURLException {
+ Region zoomTarget = createContent();
+ zoomTarget.setPrefSize(1000, 1000);
+ zoomTarget.setOnDragDetected(evt -> {
+ Node target = (Node) evt.getTarget();
+ while (target != zoomTarget && target != null) {
+ target = target.getParent();
+ }
+ if (target != null) {
+ target.startFullDrag();
+ }
+ });
+
+ Group group = new Group(zoomTarget);
+
+ // stackpane for centering the content, in case the ScrollPane viewport
+ // is larget than zoomTarget
+ StackPane content = new StackPane(group);
+ group.layoutBoundsProperty().addListener((observable, oldBounds, newBounds) -> {
+ // keep it at least as large as the content
+ content.setMinWidth(newBounds.getWidth());
+ content.setMinHeight(newBounds.getHeight());
+ });
+
+ ScrollPane scrollPane = new ScrollPane(content);
+ scrollPane.setPannable(true);
+ scrollPane.viewportBoundsProperty().addListener((observable, oldBounds, newBounds) -> {
+ // use vieport size, if not too small for zoomTarget
+ content.setPrefSize(newBounds.getWidth(), newBounds.getHeight());
+ });
+
+ content.setOnScroll(evt -> {
+ if (evt.isControlDown()) {
+ evt.consume();
+
+ final double zoomFactor = evt.getDeltaY() > 0 ? 1.2 : 1 / 1.2;
+
+ Bounds groupBounds = group.getLayoutBounds();
+ final Bounds viewportBounds = scrollPane.getViewportBounds();
+
+ // calculate pixel offsets from [0, 1] range
+ double valX = scrollPane.getHvalue() * (groupBounds.getWidth() - viewportBounds.getWidth());
+ double valY = scrollPane.getVvalue() * (groupBounds.getHeight() - viewportBounds.getHeight());
+
+ // convert content coordinates to zoomTarget coordinates
+ Point2D posInZoomTarget = zoomTarget.parentToLocal(group.parentToLocal(new Point2D(evt.getX(), evt.getY())));
+
+ // calculate adjustment of scroll position (pixels)
+ Point2D adjustment = zoomTarget.getLocalToParentTransform().deltaTransform(posInZoomTarget.multiply(zoomFactor - 1));
+
+ // do the resizing
+ zoomTarget.setScaleX(zoomFactor * zoomTarget.getScaleX());
+ zoomTarget.setScaleY(zoomFactor * zoomTarget.getScaleY());
+
+ // refresh ScrollPane scroll positions & content bounds
+ scrollPane.layout();
+
+ // convert back to [0, 1] range
+ // (too large/small values are automatically corrected by ScrollPane)
+ groupBounds = group.getLayoutBounds();
+ scrollPane.setHvalue((valX + adjustment.getX()) / (groupBounds.getWidth() - viewportBounds.getWidth()));
+ scrollPane.setVvalue((valY + adjustment.getY()) / (groupBounds.getHeight() - viewportBounds.getHeight()));
+ }
+ });
+
+ StackPane left = new StackPane(new Label("Left Menu"));
+ SplitPane root = new SplitPane(left, scrollPane);
+
+ Scene scene = new Scene(root, 800, 600);
+
+ primaryStage.setScene(scene);
+ primaryStage.show();
+ }
+
+ public static void main(String[] args) {
+ launch(args);
+ }
+
+}
\ No newline at end of file
--- /dev/null
+.highlight {
+ -fx-effect: dropshadow(three-pass-box, red, 4, 4, 0, 0);
+}
\ No newline at end of file
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc. and others
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *******************************************************************************/
+
+@org.eclipse.jdt.annotation.NonNullByDefault
+package org.lttng.scope.tmf2.views.ui.jfx.examples;
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+@org.eclipse.jdt.annotation.NonNullByDefault
+package org.lttng.scope.tmf2.views.ui.jfx;
--- /dev/null
+package org.lttng.scope.tmf2.views.ui.jfx.testapp;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.tmf.core.event.ITmfEvent;
+import org.eclipse.tracecompass.tmf.core.timestamp.ITmfTimestamp;
+import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimestamp;
+import org.eclipse.tracecompass.tmf.core.trace.ITmfContext;
+import org.eclipse.tracecompass.tmf.core.trace.TmfTrace;
+import org.eclipse.tracecompass.tmf.core.trace.location.ITmfLocation;
+import org.lttng.scope.tmf2.views.core.TimeRange;
+
+public class DummyTrace extends TmfTrace {
+
+// private static final long START_TIME = 100000;
+// private static final long END_TIME = 200000;
+
+ private static final long START_TIME = 1332170682440133097L;
+ private static final long END_TIME = 1332170692664579801L;
+
+ public DummyTrace() {
+ setTimeRange(TimeRange.of(START_TIME, END_TIME).toTmfTimeRange());
+ }
+
+ @Override
+ public ITmfTimestamp getInitialRangeOffset() {
+ return TmfTimestamp.fromNanos((long) ((END_TIME - START_TIME) * 0.1));
+ }
+
+ // ------------------------------------------------------------------------
+ // Stuff we don't use
+ // ------------------------------------------------------------------------
+
+ @Override
+ public @Nullable IStatus validate(@Nullable IProject project, @Nullable String path) {
+ return null;
+ }
+
+ @Override
+ public @Nullable IResource getResource() {
+ return null;
+ }
+
+ @Override
+ public @Nullable ITmfLocation getCurrentLocation() {
+ return null;
+ }
+
+ @Override
+ public double getLocationRatio(@Nullable ITmfLocation location) {
+ return 0;
+ }
+
+ @Override
+ public @Nullable ITmfContext seekEvent(@Nullable ITmfLocation location) {
+ return null;
+ }
+
+ @Override
+ public @Nullable ITmfContext seekEvent(double ratio) {
+ return null;
+ }
+
+ @Override
+ public @Nullable ITmfEvent parseEvent(@Nullable ITmfContext context) {
+ return null;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.ui.jfx.testapp;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
+import org.lttng.scope.tmf2.views.core.NestingBoolean;
+import org.lttng.scope.tmf2.views.core.context.ViewGroupContext;
+import org.lttng.scope.tmf2.views.core.timegraph.control.TimeGraphModelControl;
+import org.lttng.scope.tmf2.views.core.timegraph.model.provider.ITimeGraphModelProvider;
+import org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.TimeGraphWidget;
+
+import javafx.application.Application;
+import javafx.scene.Parent;
+import javafx.scene.Scene;
+import javafx.stage.Stage;
+
+public class LttngScopeApplication extends Application {
+
+ public static void main(String[] args) {
+ launch(args);
+ }
+
+ @Override
+ public void start(@Nullable Stage primaryStage) throws Exception {
+ if (primaryStage == null) {
+ return;
+ }
+
+ /* Set up the view context */
+ ViewGroupContext viewCtx = ViewGroupContext.getCurrent();
+ ITmfTrace trace = new DummyTrace();
+
+ ITimeGraphModelProvider modelProvider = new TestModelProvider();
+ TimeGraphModelControl control = new TimeGraphModelControl(viewCtx, modelProvider);
+ TimeGraphWidget widget = new TimeGraphWidget(control, new NestingBoolean());
+ control.attachView(widget);
+ Parent root = widget.getRootNode();
+
+ primaryStage.setScene(new Scene(root));
+ primaryStage.show();
+
+ viewCtx.setCurrentTrace(trace);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.ui.jfx.testapp;
+
+import java.util.List;
+import java.util.concurrent.FutureTask;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.lttng.scope.tmf2.views.core.TimeRange;
+import org.lttng.scope.tmf2.views.core.timegraph.model.provider.arrows.TimeGraphModelArrowProvider;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.ColorDefinition;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.TimeGraphEvent;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.arrows.TimeGraphArrow;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.arrows.TimeGraphArrowRender;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.arrows.TimeGraphArrowSeries;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.arrows.TimeGraphArrowSeries.LineStyle;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.tree.TimeGraphTreeElement;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.tree.TimeGraphTreeRender;
+
+import com.google.common.collect.ImmutableList;
+
+class TestModelArrowProvider1 extends TimeGraphModelArrowProvider {
+
+ public static final String SERIES_NAME = "Red";
+
+ private static final TimeGraphArrowSeries ARROW_SERIES = new TimeGraphArrowSeries(
+ SERIES_NAME,
+ new ColorDefinition(255, 0, 0),
+ LineStyle.FULL);
+
+ public TestModelArrowProvider1() {
+ super(ARROW_SERIES);
+ }
+
+ @Override
+ public TimeGraphArrowRender getArrowRender(TimeGraphTreeRender treeRender, TimeRange timeRange, @Nullable FutureTask<?> task) {
+ TimeGraphArrowSeries series = getArrowSeries();
+ List<TimeGraphTreeElement> treeElems = treeRender.getAllTreeElements();
+
+ /* Draw 3 arrows total */
+ TimeGraphEvent startEvent = new TimeGraphEvent(ts(timeRange, 0.1), treeElems.get(0));
+ TimeGraphEvent endEvent = new TimeGraphEvent(ts(timeRange, 0.3), treeElems.get(5));
+ TimeGraphArrow arrow1 = new TimeGraphArrow(startEvent, endEvent, series);
+
+ startEvent = new TimeGraphEvent(ts(timeRange, 0.2), treeElems.get(3));
+ endEvent = new TimeGraphEvent(ts(timeRange, 0.5), treeElems.get(12));
+ TimeGraphArrow arrow2 = new TimeGraphArrow(startEvent, endEvent, series);
+
+ startEvent = new TimeGraphEvent(ts(timeRange, 0.6), treeElems.get(15));
+ endEvent = new TimeGraphEvent(ts(timeRange, 0.8), treeElems.get(2));
+ TimeGraphArrow arrow3 = new TimeGraphArrow(startEvent, endEvent, series);
+
+ List<TimeGraphArrow> arrows = ImmutableList.of(arrow1, arrow2, arrow3);
+ return new TimeGraphArrowRender(timeRange, arrows);
+ }
+
+ private static long ts(TimeRange range, double ratio) {
+ return (long) (range.getDuration() * ratio + range.getStart());
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.ui.jfx.testapp;
+
+import java.util.List;
+import java.util.concurrent.FutureTask;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.lttng.scope.tmf2.views.core.TimeRange;
+import org.lttng.scope.tmf2.views.core.timegraph.model.provider.arrows.TimeGraphModelArrowProvider;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.ColorDefinition;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.TimeGraphEvent;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.arrows.TimeGraphArrow;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.arrows.TimeGraphArrowRender;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.arrows.TimeGraphArrowSeries;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.arrows.TimeGraphArrowSeries.LineStyle;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.tree.TimeGraphTreeElement;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.tree.TimeGraphTreeRender;
+
+import com.google.common.collect.ImmutableList;
+
+class TestModelArrowProvider2 extends TimeGraphModelArrowProvider {
+
+ public static final String SERIES_NAME = "Green";
+
+ private static final TimeGraphArrowSeries ARROW_SERIES = new TimeGraphArrowSeries(
+ SERIES_NAME,
+ new ColorDefinition(0, 255, 0),
+ LineStyle.FULL);
+
+ public TestModelArrowProvider2() {
+ super(ARROW_SERIES);
+ }
+
+ @Override
+ public TimeGraphArrowRender getArrowRender(TimeGraphTreeRender treeRender, TimeRange timeRange, @Nullable FutureTask<?> task) {
+ TimeGraphArrowSeries series = getArrowSeries();
+ List<TimeGraphTreeElement> treeElems = treeRender.getAllTreeElements();
+
+ /* Draw 2 arrows total */
+ TimeGraphEvent startEvent = new TimeGraphEvent(ts(timeRange, 0.3), treeElems.get(6));
+ TimeGraphEvent endEvent = new TimeGraphEvent(ts(timeRange, 0.8), treeElems.get(4));
+ TimeGraphArrow arrow1 = new TimeGraphArrow(startEvent, endEvent, series);
+
+ startEvent = new TimeGraphEvent(ts(timeRange, 0.5), treeElems.get(10));
+ endEvent = new TimeGraphEvent(ts(timeRange, 0.6), treeElems.get(7));
+ TimeGraphArrow arrow2 = new TimeGraphArrow(startEvent, endEvent, series);
+
+ List<TimeGraphArrow> arrows = ImmutableList.of(arrow1, arrow2);
+ return new TimeGraphArrowRender(timeRange, arrows);
+ }
+
+ private static long ts(TimeRange range, double ratio) {
+ return (long) (range.getDuration() * ratio + range.getStart());
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (C) 2016-2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.ui.jfx.testapp;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+import org.lttng.scope.tmf2.views.core.timegraph.model.provider.TimeGraphModelProvider;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.tree.TimeGraphTreeElement;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.tree.TimeGraphTreeRender;
+
+import com.google.common.collect.ImmutableList;
+
+class TestModelProvider extends TimeGraphModelProvider {
+
+ public static final String ENTRY_NAME_PREFIX = "Entry #";
+
+ private static final int NB_ENTRIES = 20;
+
+ private static final TimeGraphTreeRender TREE_RENDER;
+
+ static {
+ List<TimeGraphTreeElement> treeElements = IntStream.range(1, NB_ENTRIES)
+ .mapToObj(i -> new TimeGraphTreeElement(ENTRY_NAME_PREFIX + i, Collections.emptyList()))
+ .collect(Collectors.toList());
+ TimeGraphTreeElement rootElement = new TimeGraphTreeElement("Test", treeElements);
+ TREE_RENDER = new TimeGraphTreeRender(rootElement);
+ }
+
+ protected TestModelProvider() {
+ super("Test",
+ /* Sorting modes */
+ null,
+ /* Filter modes */
+ null,
+ /* State provider */
+ new TestModelStateProvider(),
+ /* Arrow providers */
+ ImmutableList.of(new TestModelArrowProvider1(), new TestModelArrowProvider2()));
+ }
+
+ @Override
+ public TimeGraphTreeRender getTreeRender() {
+ return TREE_RENDER;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (C) 2016-2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.ui.jfx.testapp;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.FutureTask;
+import java.util.stream.Collectors;
+import java.util.stream.LongStream;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.lttng.scope.tmf2.views.core.TimeRange;
+import org.lttng.scope.tmf2.views.core.config.ConfigOption;
+import org.lttng.scope.tmf2.views.core.timegraph.model.provider.states.TimeGraphModelStateProvider;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.ColorDefinition;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.LineThickness;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.states.BasicTimeGraphStateInterval;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.states.TimeGraphStateInterval;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.states.TimeGraphStateRender;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.tree.TimeGraphTreeElement;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterators;
+
+class TestModelStateProvider extends TimeGraphModelStateProvider {
+
+ /**
+ * The duration of each state is equal to its tree element index, multiplied
+ * by this factor.
+ */
+ public static final long DURATION_FACTOR = 100000000L;
+
+ public TestModelStateProvider() {
+ super(ImmutableList.of());
+ }
+
+ @Override
+ public TimeGraphStateRender getStateRender(TimeGraphTreeElement treeElement,
+ TimeRange timeRange, long resolution, @Nullable FutureTask<?> task) {
+
+ int entryIndex = Integer.valueOf(treeElement.getName().substring(TestModelProvider.ENTRY_NAME_PREFIX.length()));
+ long stateLength = entryIndex * DURATION_FACTOR;
+
+ List<TimeGraphStateInterval> intervals = LongStream.iterate(timeRange.getStart(), i -> i + stateLength)
+ .limit((timeRange.getDuration() / stateLength) + 1)
+ .mapToObj(startTime -> {
+ long endTime = startTime + stateLength - 1;
+ String name = getNextStateName();
+ ConfigOption<ColorDefinition> color = getnextStateColor();
+ return new BasicTimeGraphStateInterval(startTime, endTime, treeElement, name, name, color, LINE_THICKNESS, Collections.emptyMap());
+ })
+ .collect(Collectors.toList());
+
+ return new TimeGraphStateRender(timeRange, treeElement, intervals);
+ }
+
+ private static final ConfigOption<LineThickness> LINE_THICKNESS = new ConfigOption<>(LineThickness.NORMAL);
+ private static final Iterator<String> STATE_NAMES = Iterators.cycle("State 1", "State 2");
+ private static final Iterator<ConfigOption<ColorDefinition>> STATE_COLORS = Iterators.cycle(
+ new ConfigOption<>(new ColorDefinition(128, 0, 0)),
+ new ConfigOption<>(new ColorDefinition(0, 0, 128)));
+
+ private static synchronized String getNextStateName() {
+ return STATE_NAMES.next();
+ }
+
+ private static synchronized ConfigOption<ColorDefinition> getnextStateColor() {
+ return STATE_COLORS.next();
+ }
+
+}
--- /dev/null
+package org.lttng.scope.tmf2.views.ui.jfx.testapp;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.stream.DoubleStream;
+import java.util.stream.Stream;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.lttng.scope.tmf2.views.ui.jfx.JfxUtils;
+
+import javafx.application.Application;
+import javafx.scene.Group;
+import javafx.scene.Scene;
+import javafx.scene.control.ScrollPane;
+import javafx.scene.control.ScrollPane.ScrollBarPolicy;
+import javafx.scene.layout.BorderPane;
+import javafx.scene.layout.Pane;
+import javafx.scene.paint.Color;
+import javafx.scene.shape.Line;
+import javafx.scene.shape.Rectangle;
+import javafx.scene.shape.StrokeLineCap;
+import javafx.stage.Stage;
+
+public class UiModelApp extends Application {
+
+ /* Value where a raw Pane starts breaking down */
+ private static final double PANE_WIDTH = 1000000000.0;
+ /* Maximum pane width (roughly a 1-year trace at 0.01 nanos/pixel) */
+// private static final double PANE_WIDTH = 1e16;
+
+ private static final double MAX_WIDTH = 1000000.0;
+
+ private static final double ENTRY_HEIGHT = 20;
+ private static final Color BACKGROUD_LINES_COLOR = requireNonNull(Color.LIGHTBLUE);
+ private static final String BACKGROUND_STYLE = "-fx-background-color: rgba(255, 255, 255, 255);"; //$NON-NLS-1$
+
+ private static final double SELECTION_STROKE_WIDTH = 1;
+ private static final Color SELECTION_STROKE_COLOR = requireNonNull(Color.BLUE);
+ private static final Color SELECTION_FILL_COLOR = requireNonNull(Color.LIGHTBLUE.deriveColor(0, 1.2, 1, 0.4));
+
+ public static void main(String[] args) {
+ launch(args);
+ }
+
+ @Override
+ public void start(@Nullable Stage primaryStage) throws Exception {
+ if (primaryStage == null) {
+ return;
+ }
+
+ /* Layers */
+ Group backgroundLayer = new Group();
+ Group statesLayer = new Group();
+ Group selectionLayer = new Group();
+
+ /* Top-level */
+ Pane contentPane = new Pane(backgroundLayer, statesLayer, selectionLayer);
+ contentPane.minWidthProperty().bind(contentPane.prefWidthProperty());
+ contentPane.maxWidthProperty().bind(contentPane.prefWidthProperty());
+ contentPane.setStyle(BACKGROUND_STYLE);
+
+ ScrollPane scrollPane = new ScrollPane(contentPane);
+ scrollPane.setVbarPolicy(ScrollBarPolicy.ALWAYS);
+ scrollPane.setHbarPolicy(ScrollBarPolicy.ALWAYS);
+ scrollPane.setFitToHeight(true);
+ scrollPane.setFitToWidth(true);
+ scrollPane.setPannable(true);
+
+ BorderPane borderPane = new BorderPane(scrollPane);
+
+ primaryStage.setScene(new Scene(borderPane));
+ primaryStage.show();
+ primaryStage.setHeight(500);
+ primaryStage.setWidth(500);
+
+ contentPane.setPrefHeight(200);
+ contentPane.setPrefWidth(PANE_WIDTH);
+
+ drawBackground(backgroundLayer, contentPane);
+ drawRectangles(statesLayer);
+ drawSelection(selectionLayer, contentPane);
+ }
+
+ private static void drawBackground(Group parent, Pane content) {
+ DoubleStream.iterate(ENTRY_HEIGHT / 2, i -> i + ENTRY_HEIGHT).limit(10)
+ .mapToObj(y -> {
+ Line line = new Line();
+ line.setStartX(0);
+ line.endXProperty().bind(content.widthProperty());
+// line.setEndX(MAX_WIDTH);
+ line.setStartY(y);
+ line.setEndY(y);
+
+ line.setStroke(BACKGROUD_LINES_COLOR);
+ line.setStrokeWidth(1.0);
+ return line;
+ })
+ .forEach(parent.getChildren()::add);
+ }
+
+ private static void drawRectangles(Group target) {
+ DrawnRectangle rectangle1 = new DrawnRectangle(1, PANE_WIDTH - 20, 2);
+ rectangle1.setFill(Color.GREEN);
+
+ DrawnRectangle rectangle2 = new DrawnRectangle(20, 2000, 5);
+ rectangle2.setFill(Color.RED);
+
+ DrawnRectangle rectangle3 = new DrawnRectangle(PANE_WIDTH - 2000, PANE_WIDTH - 100, 6);
+ rectangle3.setFill(Color.ORANGE);
+
+ target.getChildren().addAll(rectangle1, rectangle2, rectangle3);
+ }
+
+
+ private static class DrawnRectangle extends Rectangle {
+
+ private static final double THICKNESS = 10;
+
+ public DrawnRectangle(double startX, double endX, int entryIndex) {
+ double y0 = entryIndex * ENTRY_HEIGHT;
+ double yOffset = (ENTRY_HEIGHT - THICKNESS) / 2;
+
+// double width = endX - startX;
+ double width = Math.min(endX - startX, MAX_WIDTH);
+
+ setX(startX);
+ setY(y0 + yOffset);
+ setWidth(width);
+ setHeight(THICKNESS);
+ }
+ }
+
+ private static void drawSelection(Group parent, Pane content) {
+ Rectangle selection1 = new Rectangle();
+ Rectangle selection2 = new Rectangle();
+
+ Stream.of(selection1, selection2).forEach(rect -> {
+ rect.setMouseTransparent(true);
+
+ rect.setStroke(SELECTION_STROKE_COLOR);
+ rect.setStrokeWidth(SELECTION_STROKE_WIDTH);
+ rect.setStrokeLineCap(StrokeLineCap.ROUND);
+ rect.setFill(SELECTION_FILL_COLOR);
+
+ rect.yProperty().bind(JfxUtils.ZERO_PROPERTY);
+ rect.heightProperty().bind(content.heightProperty());
+ });
+
+ selection1.setX(200);
+ selection1.setWidth(100);
+
+ selection2.setX(PANE_WIDTH - 1000);
+ selection2.setWidth(500);
+
+ parent.getChildren().addAll(selection1, selection2);
+ }
+}
--- /dev/null
+package org.lttng.scope.tmf2.views.ui.jfx.testapp;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.stream.DoubleStream;
+import java.util.stream.Stream;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.lttng.scope.tmf2.views.ui.jfx.JfxUtils;
+
+import javafx.application.Application;
+import javafx.scene.Group;
+import javafx.scene.Scene;
+import javafx.scene.control.ScrollPane;
+import javafx.scene.control.ScrollPane.ScrollBarPolicy;
+import javafx.scene.layout.BorderPane;
+import javafx.scene.layout.Pane;
+import javafx.scene.paint.Color;
+import javafx.scene.shape.Line;
+import javafx.scene.shape.Rectangle;
+import javafx.scene.shape.StrokeLineCap;
+import javafx.stage.Stage;
+
+public class UiModelApp2 extends Application {
+
+ /* Value where a raw Pane starts breaking down */
+// private static final double PANE_WIDTH = 1000000000.0;
+ /* Maximum supportable width due to double precision */
+ private static final double PANE_WIDTH = 1e16;
+
+ private static final double MAX_WIDTH = 1000000.0;
+
+ private static final double ENTRY_HEIGHT = 20;
+ private static final Color BACKGROUD_LINES_COLOR = requireNonNull(Color.LIGHTBLUE);
+ private static final String BACKGROUND_STYLE = "-fx-background-color: rgba(255, 255, 255, 255);"; //$NON-NLS-1$
+
+ private static final double SELECTION_STROKE_WIDTH = 1;
+ private static final Color SELECTION_STROKE_COLOR = requireNonNull(Color.BLUE);
+ private static final Color SELECTION_FILL_COLOR = requireNonNull(Color.LIGHTBLUE.deriveColor(0, 1.2, 1, 0.4));
+
+ public static void main(String[] args) {
+ launch(args);
+ }
+
+ @Override
+ public void start(@Nullable Stage primaryStage) throws Exception {
+ if (primaryStage == null) {
+ return;
+ }
+
+ /* Layers */
+ Group backgroundLayer = new Group();
+ Group statesLayer = new Group();
+ Group selectionLayer = new Group();
+
+ Pane paintingPane = new Pane(backgroundLayer, statesLayer, selectionLayer);
+ paintingPane.setStyle(BACKGROUND_STYLE);
+
+ /* Top-level */
+ Pane contentPane = new Pane(paintingPane);
+ contentPane.minWidthProperty().bind(contentPane.prefWidthProperty());
+ contentPane.maxWidthProperty().bind(contentPane.prefWidthProperty());
+
+ ScrollPane scrollPane = new ScrollPane(contentPane);
+ scrollPane.setVbarPolicy(ScrollBarPolicy.ALWAYS);
+ scrollPane.setHbarPolicy(ScrollBarPolicy.ALWAYS);
+ scrollPane.setFitToHeight(true);
+ scrollPane.setFitToWidth(true);
+ scrollPane.setPannable(true);
+
+ BorderPane borderPane = new BorderPane(scrollPane);
+
+ primaryStage.setScene(new Scene(borderPane));
+ primaryStage.show();
+ primaryStage.setHeight(500);
+ primaryStage.setWidth(500);
+
+ contentPane.setPrefHeight(200);
+ contentPane.setPrefWidth(PANE_WIDTH);
+
+ /* Bind painting pane's height to the content pane */
+ paintingPane.minHeightProperty().bind(contentPane.minHeightProperty());
+ paintingPane.prefHeightProperty().bind(contentPane.prefHeightProperty());
+ paintingPane.maxHeightProperty().bind(contentPane.maxHeightProperty());
+
+ /* We set painting's pane width programmatically */
+ paintingPane.minWidthProperty().bind(paintingPane.prefWidthProperty());
+ paintingPane.maxWidthProperty().bind(paintingPane.prefWidthProperty());
+
+ paintingPane.setPrefWidth(2000);
+ double x = PANE_WIDTH - 2000;
+ System.out.println(x);
+ paintingPane.relocate(x, 0);
+
+ drawBackground(backgroundLayer, paintingPane);
+ drawRectangles(statesLayer);
+ drawSelection(selectionLayer, paintingPane);
+ }
+
+ private static void drawBackground(Group parent, Pane parentPane) {
+ DoubleStream.iterate(ENTRY_HEIGHT / 2, i -> i + ENTRY_HEIGHT).limit(10)
+ .mapToObj(y -> {
+ Line line = new Line();
+ line.setStartX(0);
+ line.endXProperty().bind(parentPane.widthProperty());
+// line.setEndX(MAX_WIDTH);
+ line.setStartY(y);
+ line.setEndY(y);
+
+ line.setStroke(BACKGROUD_LINES_COLOR);
+ line.setStrokeWidth(1.0);
+ return line;
+ })
+ .forEach(parent.getChildren()::add);
+ }
+
+ private static void drawRectangles(Group target) {
+// DrawnRectangle rectangle1 = new DrawnRectangle(1, PANE_WIDTH - 20, 2);
+// rectangle1.setFill(Color.GREEN);
+//
+// DrawnRectangle rectangle2 = new DrawnRectangle(20, 2000, 5);
+// rectangle2.setFill(Color.RED);
+//
+// DrawnRectangle rectangle3 = new DrawnRectangle(PANE_WIDTH - 2000, PANE_WIDTH - 100, 6);
+ DrawnRectangle rectangle3 = new DrawnRectangle(100, 1900, 6);
+ rectangle3.setFill(Color.ORANGE);
+
+// target.getChildren().addAll(rectangle1, rectangle2, rectangle3);
+ target.getChildren().addAll(rectangle3);
+ }
+
+
+ private static class DrawnRectangle extends Rectangle {
+
+ private static final double THICKNESS = 10;
+
+ public DrawnRectangle(double startX, double endX, int entryIndex) {
+ double y0 = entryIndex * ENTRY_HEIGHT;
+ double yOffset = (ENTRY_HEIGHT - THICKNESS) / 2;
+
+// double width = endX - startX;
+ double width = Math.min(endX - startX, MAX_WIDTH);
+
+ setX(startX);
+ setY(y0 + yOffset);
+ setWidth(width);
+ setHeight(THICKNESS);
+ }
+ }
+
+ private static void drawSelection(Group parent, Pane content) {
+// Rectangle selection1 = new Rectangle();
+ Rectangle selection2 = new Rectangle();
+
+ Stream.of(selection2).forEach(rect -> {
+ rect.setMouseTransparent(true);
+
+ rect.setStroke(SELECTION_STROKE_COLOR);
+ rect.setStrokeWidth(SELECTION_STROKE_WIDTH);
+ rect.setStrokeLineCap(StrokeLineCap.ROUND);
+ rect.setFill(SELECTION_FILL_COLOR);
+
+ rect.yProperty().bind(JfxUtils.ZERO_PROPERTY);
+ rect.heightProperty().bind(content.heightProperty());
+ });
+//
+// selection1.setX(200);
+// selection1.setWidth(100);
+
+ selection2.setX(500);
+ selection2.setWidth(500);
+
+ parent.getChildren().addAll(selection2);
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+@org.eclipse.jdt.annotation.NonNullByDefault
+package org.lttng.scope.tmf2.views.ui.jfx.testapp;
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.ui.timeline;
+
+import static java.util.Objects.requireNonNull;
+
+import org.lttng.scope.tmf2.views.core.config.ConfigOption;
+import org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.TimeGraphWidget;
+
+import javafx.scene.paint.Color;
+import javafx.scene.paint.CycleMethod;
+import javafx.scene.paint.LinearGradient;
+import javafx.scene.paint.Paint;
+import javafx.scene.paint.Stop;
+import javafx.scene.text.Font;
+import javafx.scene.text.Text;
+
+/**
+ * Debug options for the {@link TimeGraphWidget}. Advanced users or unit
+ * tests might want to modify these.
+ *
+ * @author Alexandre Montplaisir
+ */
+public class DebugOptions {
+
+ /**
+ * Constructor using the default options
+ */
+ public DebugOptions() {
+ recomputeEllipsisWidth();
+ }
+
+ // ------------------------------------------------------------------------
+ // General options
+ // ------------------------------------------------------------------------
+
+ /**
+ * Painting flag. Indicates if automatic redrawing of the view is enabled
+ */
+ public final ConfigOption<Boolean> isPaintingEnabled = new ConfigOption<>(true);
+
+ /**
+ * Entry padding. Number of tree elements to print above *and* below the
+ * visible range
+ */
+ public final ConfigOption<Integer> entryPadding = new ConfigOption<>(5);
+
+ /**
+ * How much "padding" around the current visible window, on the left and
+ * right, should be pre-rendered. Expressed as a fraction of the current
+ * window (for example, 1.0 would render one "page" on each side).
+ */
+ public final ConfigOption<Double> renderRangePadding = new ConfigOption<>(0.1);
+
+ /**
+ * Time between UI updates, in milliseconds
+ */
+ public final ConfigOption<Integer> uiUpdateDelay = new ConfigOption<>(250);
+
+ /**
+ * Whether the view should respond to vertical or horizontal scrolling
+ * actions.
+ */
+ public final ConfigOption<Boolean> isScrollingListenersEnabled = new ConfigOption<>(true);
+
+ // ------------------------------------------------------------------------
+ // Loading overlay
+ // ------------------------------------------------------------------------
+
+ public final ConfigOption<Boolean> isLoadingOverlayEnabled = new ConfigOption<>(true);
+
+ public final ConfigOption<Color> loadingOverlayColor = new ConfigOption<>(requireNonNull(Color.GRAY));
+
+ public final ConfigOption<Double> loadingOverlayFullOpacity = new ConfigOption<>(0.3);
+ public final ConfigOption<Double> loadingOverlayTransparentOpacity = new ConfigOption<>(0.0);
+
+ public final ConfigOption<Double> loadingOverlayFadeInDuration = new ConfigOption<>(1000.0);
+ public final ConfigOption<Double> loadingOverlayFadeOutDuration = new ConfigOption<>(100.0);
+
+ // ------------------------------------------------------------------------
+ // Zoom animation
+ // ------------------------------------------------------------------------
+
+ /**
+ * The zoom animation duration, which is the amount of milliseconds it takes
+ * to complete the zoom animation (smaller number means a faster animation).
+ */
+ public final ConfigOption<Integer> zoomAnimationDuration = new ConfigOption<>(50);
+
+ /**
+ * Each zoom action (typically, one mouse-scroll == one zoom action) will
+ * increase or decrease the current visible time range by this factor.
+ */
+ public final ConfigOption<Double> zoomStep = new ConfigOption<>(0.08);
+
+ /**
+ * Each zoom action will be centered on the center of the selection if it's
+ * currently visible.
+ */
+ public final ConfigOption<Boolean> zoomPivotOnSelection = new ConfigOption<>(true);
+
+ /**
+ * Each zoom action will be centered on the current mouse position if the
+ * zoom action originates from a mouse event. If zoomPivotOnSelection is
+ * enabled, it has priority.
+ */
+ public final ConfigOption<Boolean> zoomPivotOnMousePosition = new ConfigOption<>(true);
+
+ // ------------------------------------------------------------------------
+ // State rectangles
+ // ------------------------------------------------------------------------
+
+ public final ConfigOption<Double> stateIntervalOpacity = new ConfigOption<>(1.0);
+
+ public final ConfigOption<Paint> multiStatePaint;
+ {
+ Stop[] stops = new Stop[] { new Stop(0, Color.BLACK), new Stop(1, Color.WHITE) };
+ LinearGradient lg = new LinearGradient(0, 0, 0, 1, true, CycleMethod.NO_CYCLE, stops);
+ multiStatePaint = new ConfigOption<>(lg);
+ }
+
+ // ------------------------------------------------------------------------
+ // State labels
+ // ------------------------------------------------------------------------
+
+ public final ConfigOption<Font> stateLabelFont = new ConfigOption<Font>(requireNonNull(new Text().getFont())) {
+ @Override
+ public void set(Font value) {
+ super.set(value);
+ recomputeEllipsisWidth();
+ }
+ };
+
+ public static final String ELLIPSIS_STRING = "..."; //$NON-NLS-1$
+
+ private transient double fEllipsisWidth;
+
+ public double getEllipsisWidth() {
+ return fEllipsisWidth;
+ }
+
+ private synchronized void recomputeEllipsisWidth() {
+ Text text = new Text(ELLIPSIS_STRING);
+ text.setFont(stateLabelFont.get());
+ text.applyCss();
+ fEllipsisWidth = text.getLayoutBounds().getWidth();
+ }
+
+ // ------------------------------------------------------------------------
+ // Tooltips
+ // ------------------------------------------------------------------------
+
+ public final ConfigOption<Font> toolTipFont = new ConfigOption<>(Font.font(14));
+
+ public final ConfigOption<Color> toolTipFontFill = new ConfigOption<>(requireNonNull(Color.WHITE));
+
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.ui.timeline;
+
+import org.eclipse.jdt.annotation.Nullable;
+
+import javafx.scene.Parent;
+import javafx.scene.control.ScrollPane;
+import javafx.scene.control.SplitPane;
+import javafx.scene.shape.Rectangle;
+
+public interface ITimelineWidget {
+
+ String getName();
+
+ Parent getRootNode();
+
+ void dispose();
+
+ /**
+ * Many widgets will use a SplitPane to separate a tree or info pane on the
+ * left, and a time-based pane on the right. This method is used to return
+ * this pane so the manager can apply common operations on them.
+ *
+ * @return The horizontal split pane, or 'null' if the widget doesn't use
+ * one and uses the full horizontal width of the view.
+ */
+ @Nullable SplitPane getSplitPane();
+
+ @Nullable ScrollPane getTimeBasedScrollPane();
+
+ @Nullable Rectangle getSelectionRectangle();
+
+ @Nullable Rectangle getOngoingSelectionRectangle();
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.ui.timeline;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * Message bundle for the package
+ *
+ * @noreference Messages class
+ */
+@SuppressWarnings("javadoc")
+@NonNullByDefault({})
+public class Messages extends NLS {
+
+ private static final String BUNDLE_NAME = Messages.class.getPackage().getName() + ".messages"; //$NON-NLS-1$
+
+ public static String timelineViewName;
+
+ static {
+ NLS.initializeMessages(BUNDLE_NAME, Messages.class);
+ }
+
+ private Messages() {
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.ui.timeline;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.lttng.scope.tmf2.views.core.NestingBoolean;
+import org.lttng.scope.tmf2.views.core.context.ViewGroupContext;
+import org.lttng.scope.tmf2.views.core.timegraph.control.TimeGraphModelControl;
+import org.lttng.scope.tmf2.views.core.timegraph.model.provider.ITimeGraphModelProvider;
+import org.lttng.scope.tmf2.views.core.timegraph.model.provider.ITimeGraphModelProviderFactory;
+import org.lttng.scope.tmf2.views.core.timegraph.model.provider.TimeGraphModelProviderManager;
+import org.lttng.scope.tmf2.views.core.timegraph.model.provider.TimeGraphModelProviderManager.TimeGraphOutput;
+import org.lttng.scope.tmf2.views.core.timegraph.view.TimeGraphModelView;
+import org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.TimeGraphWidget;
+
+import javafx.application.Platform;
+import javafx.beans.property.BooleanProperty;
+import javafx.beans.property.DoubleProperty;
+import javafx.beans.property.SimpleBooleanProperty;
+import javafx.beans.property.SimpleDoubleProperty;
+import javafx.scene.control.ScrollPane;
+import javafx.scene.control.SplitPane;
+import javafx.scene.shape.Rectangle;
+
+public class TimelineManager implements TimeGraphOutput {
+
+ private static final double INITIAL_DIVIDER_POSITION = 0.2;
+
+ private final TimelineView fView;
+ private final ViewGroupContext fViewContext;
+ private final Set<ITimelineWidget> fWidgets = new HashSet<>();
+
+ private final NestingBoolean fHScrollListenerStatus = new NestingBoolean();
+
+ private final DoubleProperty fDividerPosition = new SimpleDoubleProperty(INITIAL_DIVIDER_POSITION);
+ private final DoubleProperty fHScrollValue = new SimpleDoubleProperty(0);
+
+ /* Properties to sync ongoing selection rectangles */
+ private final BooleanProperty fSelectionVisible = new SimpleBooleanProperty(true);
+ private final DoubleProperty fOngoingSelectionX = new SimpleDoubleProperty();
+ private final DoubleProperty fOngoingSelectionWidth = new SimpleDoubleProperty();
+ private final BooleanProperty fOngoingSelectionVisible = new SimpleBooleanProperty(false);
+
+ public TimelineManager(TimelineView view, ViewGroupContext viewContext) {
+ fView = view;
+ fViewContext = viewContext;
+ TimeGraphModelProviderManager.instance().registerOutput(this);
+ }
+
+ @Override
+ public void providerRegistered(ITimeGraphModelProviderFactory factory) {
+ /* Instantiate a widget for this provider type */
+ ITimeGraphModelProvider provider = factory.get();
+ TimeGraphModelControl control = new TimeGraphModelControl(fViewContext, provider);
+ TimeGraphWidget viewer = new TimeGraphWidget(control, fHScrollListenerStatus);
+ control.attachView(viewer);
+
+ /*
+ * Bind properties in a runLater() statement, so that the UI views have
+ * already been initialized. The divider position, for instance, only
+ * has effect after the view is visible.
+ */
+ Platform.runLater(() -> {
+ /* Bind divider position, if applicable */
+ SplitPane splitPane = viewer.getSplitPane();
+ splitPane.getDividers().get(0).positionProperty().bindBidirectional(fDividerPosition);
+
+ /* Bind h-scrollbar position */
+ ScrollPane scrollPane = viewer.getTimeBasedScrollPane();
+ scrollPane.hvalueProperty().bindBidirectional(fHScrollValue);
+
+ /* Bind the selection rectangles together */
+ Rectangle selectionRect = viewer.getSelectionRectangle();
+ if (selectionRect != null) {
+ selectionRect.visibleProperty().bindBidirectional(fSelectionVisible);
+ }
+ Rectangle ongoingSelectionRect = viewer.getOngoingSelectionRectangle();
+ if (ongoingSelectionRect != null) {
+ ongoingSelectionRect.layoutXProperty().bindBidirectional(fOngoingSelectionX);
+ ongoingSelectionRect.widthProperty().bindBidirectional(fOngoingSelectionWidth);
+ ongoingSelectionRect.visibleProperty().bindBidirectional(fOngoingSelectionVisible);
+ }
+ });
+
+ fWidgets.add(viewer);
+ fView.addWidget(viewer.getRootNode());
+
+ }
+
+ public void dispose() {
+ TimeGraphModelProviderManager.instance().unregisterOutput(this);
+
+ fWidgets.forEach(w -> {
+ if (w instanceof TimeGraphModelView) {
+ /*
+ * TimeGraphModelView's are disposed via their control
+ *
+ * FIXME Do this better.
+ */
+ ((TimeGraphModelView) w).getControl().dispose();
+ } else {
+ w.dispose();
+ }
+ });
+ fWidgets.clear();
+ }
+
+ void resetInitialSeparatorPosition() {
+ fDividerPosition.set(INITIAL_DIVIDER_POSITION);
+ }
+
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *******************************************************************************/
+
+package org.lttng.scope.tmf2.views.ui.timeline;
+
+import static java.util.Objects.requireNonNull;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.tracecompass.tmf.ui.views.TmfView;
+import org.lttng.scope.tmf2.views.core.context.ViewGroupContext;
+import org.lttng.scope.tmf2.views.ui.jfx.JfxUtils;
+
+import javafx.embed.swt.FXCanvas;
+import javafx.geometry.Orientation;
+import javafx.scene.Node;
+import javafx.scene.Scene;
+import javafx.scene.control.SplitPane;
+
+public class TimelineView extends TmfView {
+
+ public static final String VIEW_ID = "org.lttng.scope.views.timeline"; //$NON-NLS-1$
+
+ private static final String VIEW_NAME = requireNonNull(Messages.timelineViewName);
+
+ private @Nullable TimelineManager fManager;
+ private @Nullable SplitPane fSplitPane;
+
+ public TimelineView() {
+ super(VIEW_NAME);
+ }
+
+ @Override
+ public void createPartControl(@Nullable Composite parent) {
+ if (parent == null) {
+ return;
+ }
+
+ FXCanvas fxCanvas = new FXCanvas(parent, SWT.NONE);
+ SplitPane sp = new SplitPane();
+ sp.setOrientation(Orientation.VERTICAL);
+ fSplitPane = sp;
+
+ TimelineManager manager = new TimelineManager(this, ViewGroupContext.getCurrent());
+ fManager = manager;
+
+ fxCanvas.setScene(new Scene(sp));
+
+ /*
+ * Set the initial divider positions. Has to be done *after* the
+ * Stage/Scene is initialized.
+ */
+ manager.resetInitialSeparatorPosition();
+ }
+
+ void addWidget(Node node) {
+ SplitPane sp = requireNonNull(fSplitPane);
+ JfxUtils.runOnMainThread(() -> sp.getItems().add(node));
+ }
+
+ @Override
+ public void dispose() {
+ if (fManager != null) {
+ fManager.dispose();
+ }
+ if (fSplitPane != null) {
+ fSplitPane.getItems().clear();
+ }
+ }
+
+ @Override
+ public void setFocus() {
+ }
+
+}
--- /dev/null
+###############################################################################
+# Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Eclipse Public License v1.0
+# which accompanies this distribution, and is available at
+# http://www.eclipse.org/legal/epl-v10.html
+###############################################################################
+
+timelineViewName = Timeline
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+@org.eclipse.jdt.annotation.NonNullByDefault
+package org.lttng.scope.tmf2.views.ui.timeline;
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *******************************************************************************/
+
+package org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph;
+
+import java.lang.ref.WeakReference;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import org.eclipse.jdt.annotation.Nullable;
+
+import javafx.concurrent.Task;
+
+public class LatestTaskExecutor {
+
+ private static final ExecutorService EXECUTOR = Executors.newFixedThreadPool(2);
+// private static final ExecutorService EXECUTOR = Executors.newSingleThreadExecutor();
+
+ /**
+ * The latest job that was schedule in this queue.
+ */
+ private WeakReference<@Nullable Task<?>> fLatestTask = new WeakReference<>(null);
+
+ public LatestTaskExecutor() {
+ }
+
+ public synchronized void schedule(Task<?> newTask) {
+ Task<?> latestJob = fLatestTask.get();
+ if (latestJob != null) {
+ /*
+ * Cancel the existing task. Here's hoping it cooperates and ends
+ * quickly!
+ */
+ latestJob.cancel(false);
+ }
+
+ /* Start the new job */
+ fLatestTask = new WeakReference<>(newTask);
+ EXECUTOR.submit(newTask);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.lttng.scope.tmf2.views.ui.timeline.DebugOptions;
+
+import javafx.animation.FadeTransition;
+import javafx.scene.shape.Rectangle;
+import javafx.util.Duration;
+
+class LoadingOverlay extends Rectangle {
+
+ private final DebugOptions fOpts;
+
+ private @Nullable FadeTransition fCurrentFadeIn;
+ private @Nullable FadeTransition fCurrentFadeOut;
+
+ public LoadingOverlay(DebugOptions opts) {
+ fOpts = opts;
+
+ /*
+ * Set the fill (color) by binding the property to the corresponding
+ * config option. That way if the use changes the configured value, this
+ * overlay will follow.
+ */
+ fillProperty().bind(fOpts.loadingOverlayColor);
+
+ /*
+ * The opacity property on the other hand will change through normal
+ * operation of the overlay. We are just setting the initial value here,
+ * no permanent bind.
+ */
+ setOpacity(fOpts.loadingOverlayTransparentOpacity.get());
+
+ /*
+ * The overlay should not catch mouse events. Note we could use
+ * .setPickOnBounds(false) if we wanted to handle events but also allow
+ * them to go "through".
+ */
+ setMouseTransparent(true);
+ }
+
+ public synchronized void fadeIn() {
+ if (fCurrentFadeIn != null) {
+ /* We're already fading in, let it continue. */
+ return;
+ }
+ if (fCurrentFadeOut != null) {
+ /*
+ * Don't use stop() because that would revert to the initial opacity
+ * right away.
+ */
+ fCurrentFadeOut.pause();
+ fCurrentFadeOut = null;
+ }
+
+ double fullOpacity = fOpts.loadingOverlayFullOpacity.get();
+ double fullFadeInDuration = fOpts.loadingOverlayFadeInDuration.get();
+ double startOpacity = getOpacity();
+
+ /* Do a rule-of-three to determine the duration of fade-in we need. */
+ double neededDuration = ((fullOpacity - startOpacity) / fullOpacity) * fullFadeInDuration;
+ FadeTransition fadeIn = new FadeTransition(new Duration(neededDuration), this);
+ fadeIn.setFromValue(startOpacity);
+ fadeIn.setToValue(fullOpacity);
+ fadeIn.play();
+ fCurrentFadeIn = fadeIn;
+ }
+
+ public synchronized void fadeOut() {
+ if (fCurrentFadeOut != null) {
+ /* We're already fading out, let it continue. */
+ return;
+ }
+ if (fCurrentFadeIn != null) {
+ fCurrentFadeIn.pause();
+ fCurrentFadeIn = null;
+ }
+
+ double fullOpacity = fOpts.loadingOverlayFullOpacity.get();
+ double transparentOpacity = fOpts.loadingOverlayTransparentOpacity.get();
+ double fullFadeOutDuration = fOpts.loadingOverlayFadeOutDuration.get();
+ double startOpacity = getOpacity();
+
+ /* Do a rule-of-three to determine the duration of fade-in we need. */
+ double neededDuration = (startOpacity / fullOpacity) * fullFadeOutDuration;
+ FadeTransition fadeOut = new FadeTransition(new Duration(neededDuration), this);
+ fadeOut.setFromValue(startOpacity);
+ fadeOut.setToValue(transparentOpacity);
+ fadeOut.play();
+ fCurrentFadeOut = fadeOut;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * Message bundle for the package
+ *
+ * @noreference Messages class
+ */
+@SuppressWarnings("javadoc")
+@NonNullByDefault({})
+public class Messages extends NLS {
+
+ private static final String BUNDLE_NAME = Messages.class.getPackage().getName() + ".messages"; //$NON-NLS-1$
+
+ public static String statePropertyElement;
+ public static String statePropertyStateName;
+ public static String statePropertyStartTime;
+ public static String statePropertyEndTime;
+ public static String statePropertyDuration;
+
+ static {
+ NLS.initializeMessages(BUNDLE_NAME, Messages.class);
+ }
+
+ private Messages() {
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph;
+
+import java.util.TimerTask;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.lttng.scope.tmf2.views.core.TimeRange;
+import org.lttng.scope.tmf2.views.core.context.ViewGroupContext;
+
+/**
+ * It was quickly determined that having mouse listeners start repaint tasks
+ * directly is not a good solution, since simply enqueuing a job can potentially
+ * take time, and thus makes scrolling sluggish and annoying to use.
+ *
+ * Instead, a separate thread can look periodically if the current view's
+ * position has changed since its last check, and queue UI updates as needed.
+ *
+ * This class implements such thread, as a {@link TimerTask}.
+ *
+ * @author Alexandre Montplaisir
+ */
+class PeriodicRedrawTask extends TimerTask {
+
+ /**
+ * Sequence number attached to each redraw operation. Can be used for
+ * tracing/analysis.
+ */
+ private final AtomicLong fTaskSeq = new AtomicLong();
+ private final TimeGraphWidget fViewer;
+
+ private TimeRange fPreviousHorizontalPos = ViewGroupContext.UNINITIALIZED_RANGE;
+ private VerticalPosition fPreviousVerticalPosition = VerticalPosition.UNINITIALIZED_VP;
+
+ private volatile boolean fForceRedraw = false;
+
+ public PeriodicRedrawTask(TimeGraphWidget viewer) {
+ fViewer = viewer;
+ }
+
+ @Override
+ public void run() {
+ if (!fViewer.getDebugOptions().isPaintingEnabled.get()) {
+ return;
+ }
+
+ TimeRange currentHorizontalPos = fViewer.getControl().getViewContext().getCurrentVisibleTimeRange();
+ VerticalPosition currentVerticalPos = fViewer.getCurrentVerticalPosition();
+
+ boolean movedHorizontally = !currentHorizontalPos.equals(fPreviousHorizontalPos);
+ boolean movedVertically = !currentVerticalPos.equals(fPreviousVerticalPosition);
+
+ if (fForceRedraw) {
+ fForceRedraw = false;
+ /* Then skip the next checks */
+ } else {
+ /*
+ * Skip painting if the previous position is the exact same as last
+ * time. Also skip if were not yet initialized.
+ */
+ if (!movedHorizontally && !movedVertically) {
+ return;
+ }
+ if (currentHorizontalPos.equals(ViewGroupContext.UNINITIALIZED_RANGE)
+ || currentVerticalPos.equals(VerticalPosition.UNINITIALIZED_VP)) {
+ return;
+ }
+ }
+
+ fPreviousHorizontalPos = currentHorizontalPos;
+ fPreviousVerticalPosition = currentVerticalPos;
+
+ fViewer.paintArea(currentHorizontalPos, currentVerticalPos,
+ movedHorizontally, movedVertically,
+ fTaskSeq.getAndIncrement());
+ }
+
+ public void forceRedraw() {
+ fForceRedraw = true;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Objects;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.lttng.scope.tmf2.views.core.TimeRange;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.LineThickness;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.states.TimeGraphStateInterval;
+import org.lttng.scope.tmf2.views.ui.jfx.CountingGridPane;
+import org.lttng.scope.tmf2.views.ui.jfx.JfxColorFactory;
+import org.lttng.scope.tmf2.views.ui.timeline.DebugOptions;
+
+import com.google.common.base.MoreObjects;
+
+import javafx.application.Platform;
+import javafx.geometry.Point2D;
+import javafx.scene.Node;
+import javafx.scene.control.Tooltip;
+import javafx.scene.input.MouseButton;
+import javafx.scene.paint.Paint;
+import javafx.scene.shape.Rectangle;
+import javafx.scene.text.Text;
+
+/**
+ * {@link Rectangle} object used to draw states in the timegraph. It attaches
+ * the {@link TimeGraphStateInterval} that represents this state.
+ *
+ * @author Alexandre Montplaisir
+ */
+public class StateRectangle extends Rectangle {
+
+ private final TimeGraphWidget fWidget;
+ private final int fEntryIndex;
+ private final TimeGraphStateInterval fInterval;
+
+ private @Nullable transient Paint fBaseColor;
+ private @Nullable transient Paint fSelectedColor;
+
+ private @Nullable Tooltip fTooltip = null;
+
+ /**
+ * Constructor
+ *
+ * @param viewer
+ * The viewer in which the rectangle will be placed
+ * @param interval
+ * The source interval model object
+ * @param entryIndex
+ * The index of the entry to which this state belongs.
+ */
+ public StateRectangle(TimeGraphWidget viewer, TimeGraphStateInterval interval, int entryIndex) {
+ fWidget = viewer;
+ fEntryIndex = entryIndex;
+ fInterval = interval;
+
+ /*
+ * It is possible, especially when re-opening already-indexed traces,
+ * that the indexer and the state system do not report the same
+ * start/end times. Make sure to clamp the interval's bounds to the
+ * valid values.
+ */
+ TimeRange traceRange = viewer.getControl().getViewContext().getCurrentTraceFullRange();
+ long traceStart = traceRange.getStart();
+ long intervalStart = interval.getStartTime();
+ double xStart = viewer.timestampToPaneXPos(Math.max(traceStart, intervalStart));
+
+ long traceEnd = traceRange.getEnd();
+ long intervalEndTime = interval.getEndTime();
+ double xEnd = viewer.timestampToPaneXPos(Math.min(traceEnd, intervalEndTime));
+
+ double width = Math.max(1.0, xEnd - xStart) + 1.0;
+ double height = getHeightFromThickness(interval.getLineThickness().get());
+ double y = computeY(height);
+
+ setX(0);
+ setLayoutX(xStart);
+ setY(y);
+ setWidth(width);
+ setHeight(height);
+
+ double opacity = viewer.getDebugOptions().stateIntervalOpacity.get();
+ setOpacity(opacity);
+
+ updatePaint();
+
+ /* Set initial selection state and selection listener. */
+ if (this.equals(viewer.getSelectedState())) {
+ setSelected(true);
+ viewer.setSelectedState(this, false);
+ } else {
+ setSelected(false);
+ }
+
+ setOnMouseClicked(e -> {
+ if (e.getButton() != MouseButton.PRIMARY) {
+ return;
+ }
+ viewer.setSelectedState(this, true);
+ });
+
+ /*
+ * Initialize the tooltip when the mouse enters the rectangle, if it was
+ * not done previously.
+ */
+ setOnMouseEntered(e -> generateTooltip());
+ }
+
+ private void generateTooltip() {
+ if (fTooltip != null) {
+ return;
+ }
+ TooltipContents ttContents = new TooltipContents(fWidget.getDebugOptions());
+ ttContents.addTooltipRow(Messages.statePropertyElement, fInterval.getTreeElement().getName());
+ ttContents.addTooltipRow(Messages.statePropertyStateName, fInterval.getStateName());
+ ttContents.addTooltipRow(Messages.statePropertyStartTime, fInterval.getStartTime());
+ ttContents.addTooltipRow(Messages.statePropertyEndTime, fInterval.getEndTime());
+ ttContents.addTooltipRow(Messages.statePropertyDuration, fInterval.getDuration() + " ns"); //$NON-NLS-1$
+ /* Add rows corresponding to the properties from the interval */
+ Map<String, String> properties = fInterval.getProperties();
+ properties.forEach((k, v) -> ttContents.addTooltipRow(k, v));
+
+ Tooltip tt = new Tooltip();
+ tt.setGraphic(ttContents);
+ Tooltip.install(this, tt);
+ fTooltip = tt;
+ }
+
+ /**
+ * Return the model interval representing this state
+ *
+ * @return The interval model object
+ */
+ public TimeGraphStateInterval getStateInterval() {
+ return fInterval;
+ }
+
+ public void updatePaint() {
+ /* Update the color */
+ /* Set a special paint for multi-state intervals */
+ if (fInterval.isMultiState()) {
+ Paint multiStatePaint = fWidget.getDebugOptions().multiStatePaint.get();
+ fBaseColor = multiStatePaint;
+ fSelectedColor = multiStatePaint;
+ } else {
+ fBaseColor = JfxColorFactory.getColorFromDef(fInterval.getColorDefinition().get());
+ fSelectedColor = JfxColorFactory.getDerivedColorFromDef(fInterval.getColorDefinition().get());
+ }
+ setFill(fBaseColor);
+
+ /* Update the line thickness */
+ LineThickness lt = fInterval.getLineThickness().get();
+ double height = getHeightFromThickness(lt);
+ setHeight(height);
+ /* We need to adjust the y position too */
+ setY(computeY(height));
+ }
+
+ /**
+ * Compute the Y property (the Y position of the *top* of the rectangle)
+ * this rectangle should have on its pane. This takes into consideration the
+ * entry it belongs to, as well as its target height.
+ *
+ * For example, if the line thickness of the rectangle changes, its Y has to
+ * be recomputed so that the rectangle remains centered on its entry line.
+ *
+ * This method does not change the yProperty of the rectangle.
+ */
+ private double computeY(double height) {
+ double yOffset = (TimeGraphWidget.ENTRY_HEIGHT - height) / 2;
+ double y = fEntryIndex * TimeGraphWidget.ENTRY_HEIGHT + yOffset;
+ return y;
+ }
+
+ public void setSelected(boolean isSelected) {
+ if (isSelected) {
+ setFill(fSelectedColor);
+ } else {
+ setFill(fBaseColor);
+ hideTooltip();
+ }
+ }
+
+ public void showTooltip(boolean beginning) {
+ generateTooltip();
+ Tooltip tt = requireNonNull(fTooltip);
+
+ /*
+ * Show the tooltip first, then move it to the correct location. It
+ * needs to be shown for its getWidth() etc. to be populated.
+ */
+ tt.show(this, 0, 0);
+
+ Point2D position;
+ if (beginning) {
+ /* Align to the bottom-left of the rectangle, left-aligned. */
+ /* Yes, it needs to be getX() here (0), not getLayoutX(). */
+ position = this.localToScreen(getX(), getY() + getHeight());
+ } else {
+ /* Align to the bottom-right of the rectangle, right-aligned */
+ position = this.localToScreen(getX() + getWidth() - tt.getWidth(), getY() + getHeight());
+ }
+
+ tt.setAnchorX(position.getX());
+ tt.setAnchorY(position.getY());
+ }
+
+ public void hideTooltip() {
+ Tooltip tt = fTooltip;
+ if (tt != null) {
+ Platform.runLater(() -> {
+ tt.hide();
+ });
+ }
+ }
+
+ @Override
+ protected void finalize() {
+ hideTooltip();
+ }
+
+ public static double getHeightFromThickness(LineThickness lt) {
+ switch (lt) {
+ case NORMAL:
+ default:
+ return TimeGraphWidget.ENTRY_HEIGHT - 4;
+ case SMALL:
+ return TimeGraphWidget.ENTRY_HEIGHT - 8;
+ case TINY:
+ return TimeGraphWidget.ENTRY_HEIGHT - 12;
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(fWidget, fInterval);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ StateRectangle other = (StateRectangle) obj;
+ return Objects.equals(fWidget, other.fWidget)
+ && Objects.equals(fInterval, other.fInterval);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("interval", fInterval) //$NON-NLS-1$
+ .toString();
+ }
+
+ private static class TooltipContents extends CountingGridPane {
+
+ private final DebugOptions fOpts;
+
+ public TooltipContents(DebugOptions opts) {
+ fOpts = opts;
+ }
+
+ public void addTooltipRow(Object... objects) {
+ Node[] labels = Arrays.stream(objects)
+ .map(Object::toString)
+ .map(Text::new)
+ .peek(text -> {
+ text.fontProperty().bind(fOpts.toolTipFont);
+ text.fillProperty().bind(fOpts.toolTipFontFill);
+ })
+ .toArray(Node[]::new);
+ appendRow(labels);
+ }
+ }
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc., Alexandre Montplaisir
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *******************************************************************************/
+
+package org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.Collection;
+import java.util.Timer;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.lttng.scope.tmf2.views.core.NestingBoolean;
+import org.lttng.scope.tmf2.views.core.TimeRange;
+import org.lttng.scope.tmf2.views.core.timegraph.control.TimeGraphModelControl;
+import org.lttng.scope.tmf2.views.core.timegraph.model.provider.ITimeGraphModelProvider;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.tree.TimeGraphTreeRender;
+import org.lttng.scope.tmf2.views.core.timegraph.view.TimeGraphModelView;
+import org.lttng.scope.tmf2.views.ui.timeline.DebugOptions;
+import org.lttng.scope.tmf2.views.ui.timeline.ITimelineWidget;
+import org.lttng.scope.tmf2.views.ui.timeline.TimelineView;
+import org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.layer.TimeGraphArrowLayer;
+import org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.layer.TimeGraphBackgroundLayer;
+import org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.layer.TimeGraphDrawnEventLayer;
+import org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.layer.TimeGraphSelectionLayer;
+import org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.layer.TimeGraphStateLayer;
+import org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.toolbar.ViewerToolBar;
+
+import com.google.common.annotations.VisibleForTesting;
+
+import javafx.application.Platform;
+import javafx.beans.property.DoubleProperty;
+import javafx.beans.property.SimpleDoubleProperty;
+import javafx.beans.value.ChangeListener;
+import javafx.concurrent.Task;
+import javafx.event.EventHandler;
+import javafx.geometry.Orientation;
+import javafx.scene.Group;
+import javafx.scene.Parent;
+import javafx.scene.control.ScrollPane;
+import javafx.scene.control.ScrollPane.ScrollBarPolicy;
+import javafx.scene.control.SplitPane;
+import javafx.scene.control.ToolBar;
+import javafx.scene.input.InputEvent;
+import javafx.scene.input.ScrollEvent;
+import javafx.scene.layout.BorderPane;
+import javafx.scene.layout.Pane;
+import javafx.scene.paint.Color;
+import javafx.scene.shape.Rectangle;
+
+/**
+ * Viewer for the {@link TimelineView}, encapsulating all the view's
+ * controls.
+ *
+ * Both ScrolledPanes's vertical scrollbars are bound together, so that they
+ * scroll together.
+ *
+ * @author Alexandre Montplaisir
+ */
+public class TimeGraphWidget extends TimeGraphModelView implements ITimelineWidget {
+
+ // ------------------------------------------------------------------------
+ // Style definitions
+ // (Could eventually be moved to separate .css file?)
+ // ------------------------------------------------------------------------
+
+ public static final Color BACKGROUD_LINES_COLOR = requireNonNull(Color.LIGHTBLUE);
+
+ private static final String BACKGROUND_STYLE = "-fx-background-color: rgba(255, 255, 255, 255);"; //$NON-NLS-1$
+
+ private static final int LABEL_SIDE_MARGIN = 10;
+
+ /**
+ * Height of individual entries (text + states), including padding.
+ *
+ * TODO Make this configurable (vertical zoom feature)
+ */
+ public static final double ENTRY_HEIGHT = 20;
+
+ /** Minimum allowed zoom level, in nanos per pixel */
+ private static final double ZOOM_LIMIT = 1.0;
+
+ // ------------------------------------------------------------------------
+ // Instance fields
+ // ------------------------------------------------------------------------
+
+ private final DebugOptions fDebugOptions = new DebugOptions();
+
+ private final ScrollingContext fScrollingCtx = new ScrollingContext();
+ private final ZoomActions fZoomActions = new ZoomActions();
+
+ /*
+ * Children of the time graph pane are split into groups, so we can easily
+ * redraw or add only some of them.
+ */
+ // TODO Layer for bookmarks
+ private final TimeGraphBackgroundLayer fBackgroundLayer;
+ private final TimeGraphStateLayer fStateLayer;
+ private final TimeGraphArrowLayer fArrowLayer;
+ private final TimeGraphDrawnEventLayer fDrawnEventLayer;
+ private final TimeGraphSelectionLayer fSelectionLayer;
+ private final Group fTimeGraphLoadingOverlayGroup;
+
+ private final LatestTaskExecutor fTaskExecutor = new LatestTaskExecutor();
+
+ private final NestingBoolean fHScrollListenerStatus;
+
+ private final BorderPane fBasePane;
+ private final ToolBar fToolBar;
+ private final SplitPane fSplitPane;
+
+ private final TimeGraphWidgetTreeArea fTreeArea;
+
+ private final Pane fTimeGraphPane;
+ private final ScrollPane fTimeGraphScrollPane;
+
+ private final LoadingOverlay fTimeGraphLoadingOverlay;
+
+ private final Timer fUiUpdateTimer = new Timer();
+ private final PeriodicRedrawTask fUiUpdateTimerTask = new PeriodicRedrawTask(this);
+
+ private volatile TimeGraphTreeRender fLatestTreeRender = TimeGraphTreeRender.EMPTY_RENDER;
+
+ /** Current zoom level */
+ private final DoubleProperty fNanosPerPixel = new SimpleDoubleProperty(1.0);
+
+ /**
+ * Constructor
+ *
+ * @param control
+ * The control for this widget. See
+ * {@link TimeGraphModelControl}.
+ * @param hScrollListenerStatus
+ * If the hscroll property of this widget's scrollpane is bound
+ * with others (possibly through the
+ * {@link ITimelineWidget#getTimeBasedScrollPane()} method), then
+ * a common {@link NestingBoolean} should be used to track
+ * requests to disable the hscroll listener.
+ * <p>
+ * If the widget is to be used stand-alone, then you can pass a "
+ * <code>new NestingBoolean()</code> " that only this view will
+ * use.
+ */
+ public TimeGraphWidget(TimeGraphModelControl control, NestingBoolean hScrollListenerStatus) {
+ super(control);
+ fHScrollListenerStatus = hScrollListenerStatus;
+
+ // --------------------------------------------------------------------
+ // Prepare the tree part's scene graph
+ // --------------------------------------------------------------------
+
+ fTreeArea = new TimeGraphWidgetTreeArea(ENTRY_HEIGHT, getControl().getModelRenderProvider().traceProperty());
+
+ // --------------------------------------------------------------------
+ // Prepare the time graph's part scene graph
+ // --------------------------------------------------------------------
+
+ fTimeGraphLoadingOverlay = new LoadingOverlay(fDebugOptions);
+ fTimeGraphLoadingOverlayGroup = new Group(fTimeGraphLoadingOverlay);
+
+ fTimeGraphPane = new Pane();
+ fBackgroundLayer = new TimeGraphBackgroundLayer(this, new Group());
+ fStateLayer = new TimeGraphStateLayer(this, new Group());
+ fArrowLayer = new TimeGraphArrowLayer(this, new Group());
+ fDrawnEventLayer = new TimeGraphDrawnEventLayer(this, new Group());
+ fSelectionLayer = new TimeGraphSelectionLayer(this, new Group());
+
+ /*
+ * The order of the layers is important here, it will go from back to
+ * front.
+ */
+ fTimeGraphPane.getChildren().addAll(fBackgroundLayer.getParentGroup(),
+ fStateLayer.getParentGroup(),
+ fStateLayer.getLabelGroup(),
+ fArrowLayer.getParentGroup(),
+ fDrawnEventLayer.getParentGroup(),
+ fSelectionLayer.getParentGroup(),
+ fTimeGraphLoadingOverlayGroup);
+
+ fTimeGraphPane.setStyle(BACKGROUND_STYLE);
+
+ /*
+ * We control the width of the time graph pane programmatically, so
+ * ensure that calls to setPrefWidth set the actual width right away.
+ */
+ fTimeGraphPane.minWidthProperty().bind(fTimeGraphPane.prefWidthProperty());
+ fTimeGraphPane.maxWidthProperty().bind(fTimeGraphPane.prefWidthProperty());
+
+ /*
+ * Ensure the time graph pane is always exactly the same vertical size
+ * as the tree pane, so they remain aligned.
+ */
+
+ fTimeGraphPane.minHeightProperty().bind(fTreeArea.currentHeightProperty());
+ fTimeGraphPane.prefHeightProperty().bind(fTreeArea.currentHeightProperty());
+ fTimeGraphPane.maxHeightProperty().bind(fTreeArea.currentHeightProperty());
+
+ /*
+ * Setup clipping on the timegraph pane, meaning its children outside of its
+ * actual boundary should not be rendered. For example, when the tree gets
+ * collapsed, data for hidden entries should be hidden too.
+ */
+ Rectangle clipRect = new Rectangle();
+ clipRect.setX(0);
+ clipRect.setY(0);
+ clipRect.widthProperty().bind(fTimeGraphPane.widthProperty());
+ clipRect.heightProperty().bind(fTimeGraphPane.heightProperty());
+ fTimeGraphPane.setClip(clipRect);
+
+ /*
+ * Set the loading overlay's size to always follow the size of the pane.
+ */
+ fTimeGraphLoadingOverlay.widthProperty().bind(fTimeGraphPane.widthProperty());
+ fTimeGraphLoadingOverlay.heightProperty().bind(fTimeGraphPane.heightProperty());
+
+ fTimeGraphScrollPane = new ScrollPane(fTimeGraphPane);
+ fTimeGraphScrollPane.setVbarPolicy(ScrollBarPolicy.ALWAYS);
+ fTimeGraphScrollPane.setHbarPolicy(ScrollBarPolicy.ALWAYS);
+ fTimeGraphScrollPane.setFitToHeight(true);
+ fTimeGraphScrollPane.setFitToWidth(true);
+ fTimeGraphScrollPane.setPannable(true);
+
+ /*
+ * Attach the scrollbar listener
+ *
+ * TODO Move this to the timeline ?
+ */
+ fTimeGraphScrollPane.hvalueProperty().addListener(fScrollingCtx.fHScrollChangeListener);
+
+ /*
+ * Mouse scroll handlers (for zooming) are attached to the time graph
+ * itself: events let through will be used by the scrollpane as normal
+ * scroll actions.
+ */
+ fTimeGraphPane.setOnScroll(fMouseScrollListener);
+
+ /*
+ * Upon reception of any mouse/keyboard event, if there's still a drawn
+ * tooltip it should be hidden.
+ */
+ fTimeGraphPane.addEventFilter(InputEvent.ANY, e -> {
+ StateRectangle selectedState = fSelectedState;
+ if (selectedState != null) {
+ selectedState.hideTooltip();
+ }
+ /* We must not consume the event here */
+ });
+
+ /* Synchronize the two scrollpanes' vertical scroll bars together */
+ fTreeArea.getVerticalScrollBar().valueProperty().bindBidirectional(fTimeGraphScrollPane.vvalueProperty());
+
+ // --------------------------------------------------------------------
+ // Prepare the top-level area
+ // --------------------------------------------------------------------
+
+ fToolBar = new ViewerToolBar(this);
+
+ fSplitPane = new SplitPane(fTreeArea, fTimeGraphScrollPane);
+ fSplitPane.setOrientation(Orientation.HORIZONTAL);
+
+ fBasePane = new BorderPane();
+ fBasePane.setCenter(fSplitPane);
+ fBasePane.setTop(fToolBar);
+
+ /* Start the periodic redraw thread */
+ long delay = fDebugOptions.uiUpdateDelay.get();
+ fUiUpdateTimer.schedule(fUiUpdateTimerTask, delay, delay);
+ }
+
+ public TimeGraphTreeRender getLatestTreeRender() {
+ return fLatestTreeRender;
+ }
+
+ // ------------------------------------------------------------------------
+ // ITimelineWidget
+ // ------------------------------------------------------------------------
+
+ @Override
+ public String getName() {
+ return getControl().getModelRenderProvider().getName();
+ }
+
+ @Override
+ public Parent getRootNode() {
+ return fBasePane;
+ }
+
+ @Override
+ public @NonNull SplitPane getSplitPane() {
+ return fSplitPane;
+ }
+
+ @Override
+ public @NonNull ScrollPane getTimeBasedScrollPane() {
+ return fTimeGraphScrollPane;
+ }
+
+ @Override
+ public @Nullable Rectangle getSelectionRectangle() {
+ return fSelectionLayer.getSelectionRectangle();
+ }
+
+ @Override
+ public @Nullable Rectangle getOngoingSelectionRectangle() {
+ return fSelectionLayer.getOngoingSelectionRectangle();
+ }
+
+ // ------------------------------------------------------------------------
+ // Operations
+ // ------------------------------------------------------------------------
+
+ @Override
+ public void disposeImpl() {
+ /* Stop/cleanup the redraw thread */
+ fUiUpdateTimer.cancel();
+ fUiUpdateTimer.purge();
+ }
+
+ @Override
+ public void clear() {
+ Platform.runLater(() -> {
+ /*
+ * Clear the generated children of the various groups so they go
+ * back to their initial (post-constructor) state.
+ */
+ fTreeArea.clear();
+
+ fBackgroundLayer.clear();
+ fStateLayer.clear();
+ fArrowLayer.clear();
+ fDrawnEventLayer.clear();
+
+ /* Also clear whatever cached objects the viewer currently has. */
+ fLatestTreeRender = TimeGraphTreeRender.EMPTY_RENDER;
+ fUiUpdateTimerTask.forceRedraw();
+ });
+ }
+
+ @Override
+ public void seekVisibleRange(TimeRange newVisibleRange) {
+ final TimeRange fullTimeGraphRange = getViewContext().getCurrentTraceFullRange();
+
+ /* Update the zoom level */
+ long windowTimeRange = newVisibleRange.getDuration();
+ double timeGraphVisibleWidth = fTimeGraphScrollPane.getViewportBounds().getWidth();
+ /* Clamp the width to 1 px (0 is reported if the view is not visible) */
+ timeGraphVisibleWidth = Math.max(1, timeGraphVisibleWidth);
+ fNanosPerPixel.set(windowTimeRange / timeGraphVisibleWidth);
+
+ double oldTotalWidth = fTimeGraphPane.getLayoutBounds().getWidth();
+ double newTotalWidth = timestampToPaneXPos(fullTimeGraphRange.getEnd()) - timestampToPaneXPos(fullTimeGraphRange.getStart());
+ if (newTotalWidth < 1.0) {
+ // FIXME
+ return;
+ }
+
+ double newValue;
+ if (newVisibleRange.getStart() == fullTimeGraphRange.getStart()) {
+ newValue = fTimeGraphScrollPane.getHmin();
+ } else if (newVisibleRange.getEnd() == fullTimeGraphRange.getEnd()) {
+ newValue = fTimeGraphScrollPane.getHmax();
+ } else {
+ /*
+ * The "hvalue" is in reference to the beginning of the pane, not
+ * the middle point as one could think.
+ *
+ * Also note that the "scrollable distance" is not simply
+ * "timeGraphTotalWidth", it's
+ * "timeGraphTotalWidth - timeGraphVisibleWidth". The view does not
+ * allow scrolling the start and end edges up to the middle point
+ * for example.
+ *
+ * See http://stackoverflow.com/a/23518314/4227853 for a great
+ * explanation.
+ */
+ double startPos = timestampToPaneXPos(newVisibleRange.getStart());
+ newValue = startPos / (newTotalWidth - timeGraphVisibleWidth);
+ }
+
+ fHScrollListenerStatus.disable();
+ try {
+
+ /*
+ * If the zoom level changed, resize the pane and relocate its
+ * current contents. That way the "intermediate" display before the
+ * next repaint will continue showing correct data.
+ */
+ if (Math.abs(newTotalWidth - oldTotalWidth) > 0.5) {
+
+ /* Resize/reposition the state rectangles */
+ double factor = (newTotalWidth / oldTotalWidth);
+ fStateLayer.getRenderedStateRectangles().forEach(rect -> {
+ rect.setLayoutX(rect.getLayoutX() * factor);
+ rect.setWidth(rect.getWidth() * factor);
+ });
+
+ /* Reposition the text labels (don't stretch them!) */
+ fStateLayer.getRenderedStateLabels().forEach(text -> {
+ text.setX(text.getX() * factor);
+ });
+
+ /* Reposition the arrows */
+ fArrowLayer.getRenderedArrows().forEach(arrow -> {
+ arrow.setStartX(arrow.getStartX() * factor);
+ arrow.setEndX(arrow.getEndX() * factor);
+ });
+
+ /* Reposition the drawn events */
+ fDrawnEventLayer.getRenderedEvents().forEach(event -> {
+ /*
+ * Drawn events use the "translate" properties to define
+ * their position.
+ */
+ event.setTranslateX(event.getTranslateX() * factor);
+ });
+
+
+ /*
+ * Resize the pane itself. Remember min/max are bound to the
+ * "pref" width, so this will change the actual size right away.
+ */
+ fTimeGraphPane.setPrefWidth(newTotalWidth);
+ /*
+ * Since we changed the size of a child of the scrollpane, it's
+ * important to call layout() on it before setHvalue(). If we
+ * don't, the setHvalue() will apply to the old layout, and the
+ * upcoming pulse will simply revert our changes.
+ */
+ fTimeGraphScrollPane.layout();
+ }
+
+ fTimeGraphScrollPane.setHvalue(newValue);
+
+ } finally {
+ fHScrollListenerStatus.enable();
+ }
+
+ /*
+ * Redraw the current selection, as it may have moved if we changed the
+ * size of the pane.
+ */
+ redrawSelection();
+ }
+
+ /**
+ *
+ * Paint the specified view area.
+ *
+ * @param windowRange
+ * The horizontal position where the visible window currently is
+ * @param verticalPos
+ * The vertical position where the visible window currently is
+ * @param movedHorizontally
+ * If we have moved horizontally since the last redraw. May be
+ * used to skip some operations. If you are not sure say "true".
+ * @param movedVertically
+ * If we have moved vertically since the last redraw. May be used
+ * to skip some operations. If you are not sure say "true".
+ * @param taskSeqNb
+ * The sequence number of this task, used for logging only
+ */
+ void paintArea(TimeRange windowRange, VerticalPosition verticalPos,
+ boolean movedHorizontally, boolean movedVertically,
+ long taskSeqNb) {
+ final TimeRange fullTimeGraphRange = getViewContext().getCurrentTraceFullRange();
+
+ /*
+ * Request the needed renders and prepare the corresponding UI objects.
+ * We may ask for some padding on each side, clamped by the trace's
+ * start and end.
+ */
+ final long timeRangePadding = Math.round(windowRange.getDuration() * fDebugOptions.renderRangePadding.get());
+ final long renderingStartTime = Math.max(fullTimeGraphRange.getStart(), windowRange.getStart() - timeRangePadding);
+ final long renderingEndTime = Math.min(fullTimeGraphRange.getEnd(), windowRange.getEnd() + timeRangePadding);
+ final TimeRange renderingRange = TimeRange.of(renderingStartTime, renderingEndTime);
+
+ /*
+ * Start a new repaint, display the "loading" overlay. The next
+ * paint task to finish will put it back to non-visible.
+ */
+ if (getDebugOptions().isLoadingOverlayEnabled.get()) {
+ fTimeGraphLoadingOverlay.fadeIn();
+ }
+
+ Task<@Nullable Void> task = new Task<@Nullable Void>() {
+ @Override
+ protected @Nullable Void call() {
+ System.err.println("Starting paint task #" + taskSeqNb);
+
+ ITimeGraphModelProvider modelProvider = getControl().getModelRenderProvider();
+ TimeGraphTreeRender treeRender = modelProvider.getTreeRender();
+
+ if (isCancelled()) {
+ return null;
+ }
+
+ /* Prepare the tree part, if needed */
+ if (!treeRender.equals(fLatestTreeRender)) {
+ fLatestTreeRender = treeRender;
+ fTreeArea.updateTreeContents(treeRender);
+ }
+
+ if (isCancelled()) {
+ return null;
+ }
+
+ /* Paint the background. It's very quick so we can do it every time. */
+ fBackgroundLayer.drawContents(treeRender, renderingRange, verticalPos, this);
+
+ /*
+ * The state rectangles should be redrawn as soon as we move,
+ * either horizontally or vertically.
+ */
+ fStateLayer.setWindowRange(windowRange);
+ fStateLayer.drawContents(treeRender, renderingRange, verticalPos, this);
+
+ if (isCancelled()) {
+ return null;
+ }
+
+ /*
+ * Arrows and drawn events are drawn for the full vertical
+ * range. Only refetch/repaint them if we moved horizontally.
+ */
+ if (movedHorizontally) {
+ fArrowLayer.drawContents(treeRender, renderingRange, verticalPos, this);
+ fDrawnEventLayer.drawContents(treeRender, renderingRange, verticalPos, this);
+ }
+
+ if (isCancelled()) {
+ return null;
+ }
+
+ /* Painting is finished, turn off the loading overlay */
+ Platform.runLater(() -> {
+ System.err.println("fading out overlay");
+ fTimeGraphLoadingOverlay.fadeOut();
+ if (fRepaintLatch != null) {
+ fRepaintLatch.countDown();
+ }
+ });
+
+ return null;
+ }
+ };
+
+ System.err.println("Queueing task #" + taskSeqNb);
+
+ /*
+ * Attach a listener to the task to receive exceptions thrown within the
+ * task.
+ */
+ task.exceptionProperty().addListener((obs, oldVal, newVal) -> {
+ if (newVal != null) {
+ newVal.printStackTrace();
+ }
+ });
+
+ fTaskExecutor.schedule(task);
+ }
+
+ @Override
+ public void drawSelection(TimeRange selectionRange) {
+ fSelectionLayer.drawSelection(selectionRange);
+ }
+
+ private void redrawSelection() {
+ TimeRange selectionRange = getViewContext().getCurrentSelectionTimeRange();
+ drawSelection(selectionRange);
+ }
+
+ private @Nullable StateRectangle fSelectedState = null;
+
+ /**
+ * Set the selected state rectangle
+ *
+ * @param state
+ * The new selected state. It should ideally be one that's
+ * present in the scenegraph.
+ * @param deselectPrevious
+ * If the previously selected interval should be unmarked as
+ * selected.
+ */
+ public void setSelectedState(StateRectangle state, boolean deselectPrevious) {
+ @Nullable StateRectangle previousSelectedState = fSelectedState;
+ if (previousSelectedState != null) {
+ previousSelectedState.hideTooltip();
+ if (deselectPrevious) {
+ previousSelectedState.setSelected(false);
+ }
+ }
+
+ state.setSelected(true);
+ fSelectedState = state;
+ }
+
+ /**
+ * Get the currently selected state interval
+ *
+ * @return The current selected state
+ */
+ public @Nullable StateRectangle getSelectedState() {
+ return fSelectedState;
+ }
+
+ /**
+ * Return all state rectangles currently present in the timegraph.
+ *
+ * @return The rendered state rectangles
+ */
+ public Collection<StateRectangle> getRenderedStateRectangles() {
+ return fStateLayer.getRenderedStateRectangles();
+ }
+
+ // ------------------------------------------------------------------------
+ // Mouse event listeners
+ // ------------------------------------------------------------------------
+
+ /**
+ * Class encapsulating the scrolling operations of the time graph pane.
+ *
+ * The mouse entered/exited handlers ensure only the scrollpane being
+ * interacted by the user is the one sending the synchronization signals.
+ */
+ private class ScrollingContext {
+
+ /**
+ * Listener for the horizontal scrollbar changes
+ */
+ private final ChangeListener<Number> fHScrollChangeListener = (observable, oldValue, newValue) -> {
+ if (!fDebugOptions.isScrollingListenersEnabled.get()) {
+ System.out.println("HScroll event ignored due to debug option");
+ return;
+ }
+ if (!fHScrollListenerStatus.enabledProperty().get()) {
+ System.out.println("HScroll listener triggered but inactive");
+ return;
+ }
+
+ System.out.println("HScroll change listener triggered, oldval=" + oldValue.toString() + ", newval=" + newValue.toString());
+
+ /* We need to specify the new value here, or else the old one will be used */
+ TimeRange range = getTimeGraphEdgeTimestamps(newValue.doubleValue());
+
+ System.out.println("Sending visible range update: " + range.toString());
+
+ getControl().updateVisibleTimeRange(range, false);
+
+ /*
+ * We ask the control to not send this signal back to us (to avoid
+ * jitter while scrolling), but the next UI update should refresh
+ * the view accordingly.
+ *
+ * It is not our responsibility to update to this
+ * HorizontalPosition. The control will update accordingly upon
+ * managing the signal we just sent.
+ */
+ };
+ }
+
+ /**
+ * Event handler attached to the *time graph pane*, to execute zooming
+ * operations when the control key is down (otherwise, it just lets the even
+ * bubble to the ScrollPane, which will do a standard scroll).
+ */
+ private final EventHandler<ScrollEvent> fMouseScrollListener = e -> {
+ boolean forceUseMousePosition = false;
+
+ if (!e.isControlDown()) {
+ return;
+ }
+
+ if (e.isShiftDown()) {
+ forceUseMousePosition = true;
+ }
+ e.consume();
+
+ double delta = e.getDeltaY();
+ boolean zoomIn = (delta > 0.0); // false means a zoom-out
+
+ /*
+ * getX() corresponds to the X position of the mouse on the time graph.
+ * This is seriously awesome.
+ */
+ fZoomActions.zoom(zoomIn, forceUseMousePosition, e.getX());
+
+ };
+
+ // ------------------------------------------------------------------------
+ // View-specific actions
+ // These do not come from the control, but from the view itself
+ // ------------------------------------------------------------------------
+
+ /**
+ * Utils class encapsulating zoom operations
+ */
+ public class ZoomActions {
+
+ public void zoom(boolean zoomIn, boolean forceUseMousePosition, @Nullable Double mouseX) {
+ final double zoomStep = fDebugOptions.zoomStep.get();
+
+ double newScaleFactor = (zoomIn ? 1.0 * (1 + zoomStep) : 1.0 * (1 / (1 + zoomStep)));
+
+ /* Send a corresponding window-range signal to the control */
+ TimeGraphModelControl control = getControl();
+ TimeRange visibleRange = getViewContext().getCurrentVisibleTimeRange();
+
+ TimeRange currentSelection = getViewContext().getCurrentSelectionTimeRange();
+ long currentSelectionCenter = ((currentSelection.getDuration() / 2) + currentSelection.getStart());
+ boolean currentSelectionCenterIsVisible = visibleRange.contains(currentSelectionCenter);
+
+ long zoomPivot;
+ if (fDebugOptions.zoomPivotOnMousePosition.get() && mouseX != null && forceUseMousePosition) {
+ /* Pivot on mouse position */
+ zoomPivot = paneXPosToTimestamp(mouseX);
+ } else if (fDebugOptions.zoomPivotOnSelection.get() && currentSelectionCenterIsVisible) {
+ /* Pivot on current selection center */
+ zoomPivot = currentSelectionCenter;
+ } else if (fDebugOptions.zoomPivotOnMousePosition.get() && mouseX != null) {
+ /* Pivot on mouse position */
+ zoomPivot = paneXPosToTimestamp(mouseX);
+ } else {
+ /* Pivot on center of visible range */
+ zoomPivot = visibleRange.getStart() + (visibleRange.getDuration() / 2);
+ }
+
+ /* Prevent going closer than the zoom limit */
+ double timeGraphVisibleWidth = Math.max(1, fTimeGraphScrollPane.getViewportBounds().getWidth());
+ double minDuration = ZOOM_LIMIT * timeGraphVisibleWidth;
+
+ double newDuration = visibleRange.getDuration() * (1.0 / newScaleFactor);
+ newDuration = Math.max(minDuration, newDuration);
+ double durationDelta = newDuration - visibleRange.getDuration();
+ double zoomPivotRatio = (double) (zoomPivot - visibleRange.getStart()) / (double) (visibleRange.getDuration());
+
+ long newStart = visibleRange.getStart() - Math.round(durationDelta * zoomPivotRatio);
+ long newEnd = visibleRange.getEnd() + Math.round(durationDelta - (durationDelta * zoomPivotRatio));
+
+ /* Clamp newStart and newEnd to the full trace's range */
+ TimeRange fullRange = control.getViewContext().getCurrentTraceFullRange();
+ long traceStart = fullRange.getStart();
+ long traceEnd = fullRange.getEnd();
+ newStart = Math.max(newStart, traceStart);
+ newEnd = Math.min(newEnd, traceEnd);
+
+ control.updateVisibleTimeRange(TimeRange.of(newStart, newEnd), true);
+ }
+
+ }
+
+ /**
+ * Get the viewer's zoom actions
+ *
+ * @return The zoom actions
+ */
+ public ZoomActions getZoomActions() {
+ return fZoomActions;
+ }
+
+ // ------------------------------------------------------------------------
+ // Common utils
+ // ------------------------------------------------------------------------
+
+ /**
+ * Determine the timestamps currently represented by the left and right
+ * edges of the time graph pane. In other words, the current "visible range"
+ * the view is showing.
+ *
+ * Note that this method gets its information from UI objects only, so there
+ * might be discrepancies between this and the results of
+ * {@link TimeGraphModelControl#getVisibleTimeRange()}.
+ *
+ * @param newHValue
+ * The "hvalue" property of the horizontal scrollbar to use. If
+ * null, the current value will be retrieved from the scenegraph
+ * object. For example, a scrolling listener might want to pass
+ * its newValue here, since the scenegraph object will not have
+ * been updated yet.
+ * @return The corresponding time range
+ */
+ TimeRange getTimeGraphEdgeTimestamps(@Nullable Double newHValue) {
+ double hvalue = (newHValue == null ? fTimeGraphScrollPane.getHvalue() : newHValue.doubleValue());
+
+ /*
+ * Determine the X positions represented by the edges.
+ */
+ double hmin = fTimeGraphScrollPane.getHmin();
+ double hmax = fTimeGraphScrollPane.getHmax();
+ double contentWidth = fTimeGraphPane.getLayoutBounds().getWidth();
+ double viewportWidth = fTimeGraphScrollPane.getViewportBounds().getWidth();
+ double hoffset = Math.max(0, contentWidth - viewportWidth) * (hvalue - hmin) / (hmax - hmin);
+
+ /*
+ * Convert the positions of the left and right edges to timestamps.
+ */
+ long tsStart = paneXPosToTimestamp(hoffset);
+ long tsEnd = paneXPosToTimestamp(hoffset + viewportWidth);
+
+ return TimeRange.of(tsStart, tsEnd);
+ }
+
+ public double timestampToPaneXPos(long timestamp) {
+ TimeRange fullTimeGraphRange = getViewContext().getCurrentTraceFullRange();
+ return timestampToPaneXPos(timestamp, fullTimeGraphRange, fNanosPerPixel.get());
+ }
+
+ @VisibleForTesting
+ static double timestampToPaneXPos(long timestamp, TimeRange fullTimeGraphRange, double nanosPerPixel) {
+ long start = fullTimeGraphRange.getStart();
+ long end = fullTimeGraphRange.getEnd();
+
+ if (timestamp < start) {
+ throw new IllegalArgumentException(timestamp + " is smaller than trace start time " + start); //$NON-NLS-1$
+ }
+ if (timestamp > end) {
+ throw new IllegalArgumentException(timestamp + " is greater than trace end time " + end); //$NON-NLS-1$
+ }
+
+ double traceDuration = fullTimeGraphRange.getDuration();
+ double timeStampRatio = (timestamp - start) / traceDuration;
+
+ long fullTraceWidthInPixels = (long) (traceDuration / nanosPerPixel);
+ double xPos = fullTraceWidthInPixels * timeStampRatio;
+ return Math.round(xPos);
+ }
+
+ public long paneXPosToTimestamp(double x) {
+ long fullTimeGraphStartTime = getViewContext().getCurrentTraceFullRange().getStart();
+ return paneXPosToTimestamp(x, fTimeGraphPane.getWidth(), fullTimeGraphStartTime, fNanosPerPixel.get());
+ }
+
+ @VisibleForTesting
+ static long paneXPosToTimestamp(double x, double totalWidth, long startTimestamp, double nanosPerPixel) {
+ if (x < 0.0 || totalWidth < 1.0 || x > totalWidth) {
+ throw new IllegalArgumentException("Invalid position arguments: pos=" + x + ", width=" + totalWidth);
+ }
+
+ long ts = Math.round(x * nanosPerPixel);
+ return ts + startTimestamp;
+ }
+
+ /**
+ * Get the current vertical position of the timegraph.
+ *
+ * @return The corresponding VerticalPosition
+ */
+ VerticalPosition getCurrentVerticalPosition() {
+ double vvalue = fTimeGraphScrollPane.getVvalue();
+
+ /* Get the Y position of the top/bottom edges of the pane */
+ double vmin = fTimeGraphScrollPane.getVmin();
+ double vmax = fTimeGraphScrollPane.getVmax();
+ double contentHeight = fTimeGraphPane.getLayoutBounds().getHeight();
+ double viewportHeight = fTimeGraphScrollPane.getViewportBounds().getHeight();
+
+ double vtop = Math.max(0, contentHeight - viewportHeight) * (vvalue - vmin) / (vmax - vmin);
+ double vbottom = vtop + viewportHeight;
+
+ return new VerticalPosition(vtop, vbottom);
+ }
+
+ public static int paneYPosToEntryListIndex(double yPos, double entryHeight) {
+ if (yPos < 0.0 || entryHeight < 0.0) {
+ throw new IllegalArgumentException();
+ }
+
+ return (int) (yPos / entryHeight);
+ }
+
+ // ------------------------------------------------------------------------
+ // Test accessors
+ // ------------------------------------------------------------------------
+
+ private volatile @Nullable CountDownLatch fRepaintLatch = null;
+
+ @VisibleForTesting
+ void prepareWaitForRepaint() {
+ if (fRepaintLatch != null) {
+ throw new IllegalStateException("Do not call this method concurrently!"); //$NON-NLS-1$
+ }
+ fRepaintLatch = new CountDownLatch(1);
+ }
+
+ @VisibleForTesting
+ boolean waitForRepaint() {
+ CountDownLatch latch = fRepaintLatch;
+ boolean done = false;
+ if (latch == null) {
+ throw new IllegalStateException("Do not call this method concurrently!"); //$NON-NLS-1$
+ }
+ try {
+ done = latch.await(100, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ }
+ if (done) {
+ fRepaintLatch = null;
+ }
+ return done;
+ }
+
+ /**
+ * Bypass the redraw thread and do a manual redraw of the current location.
+ */
+ @VisibleForTesting
+ void paintCurrentLocation() {
+ TimeRange currentHorizontalPos = getViewContext().getCurrentVisibleTimeRange();
+ VerticalPosition currentVerticalPos = getCurrentVerticalPosition();
+ paintArea(currentHorizontalPos, currentVerticalPos, true, true, 0);
+ }
+
+ // could eventually be exposed to the user, as "advanced preferences"
+ public DebugOptions getDebugOptions() {
+ return fDebugOptions;
+ }
+
+ public double getCurrentNanosPerPixel() {
+ return fNanosPerPixel.get();
+ }
+
+ public Pane getTimeGraphPane() {
+ return fTimeGraphPane;
+ }
+
+ @VisibleForTesting
+ ScrollPane getTimeGraphScrollPane() {
+ return fTimeGraphScrollPane;
+ }
+
+ @VisibleForTesting
+ TimeGraphArrowLayer getArrowLayer() {
+ return fArrowLayer;
+ }
+
+ @VisibleForTesting
+ TimeGraphDrawnEventLayer getDrawnEventLayer() {
+ return fDrawnEventLayer;
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.tree.TimeGraphTreeElement;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.tree.TimeGraphTreeRender;
+import org.lttng.scope.tmf2.views.ui.jfx.JfxUtils;
+
+import com.sun.javafx.scene.control.skin.TreeViewSkin;
+import com.sun.javafx.scene.control.skin.VirtualFlow;
+
+import javafx.application.Platform;
+import javafx.beans.binding.DoubleBinding;
+import javafx.beans.property.ObjectProperty;
+import javafx.geometry.Insets;
+import javafx.geometry.Orientation;
+import javafx.scene.control.ScrollBar;
+import javafx.scene.control.ScrollPane;
+import javafx.scene.control.TreeCell;
+import javafx.scene.control.TreeItem;
+import javafx.scene.control.TreeView;
+import javafx.scene.layout.BorderPane;
+
+/**
+ * Extension of {@link TreeView} which exposes some of its components, like
+ * Scrollbars.
+ *
+ * It also supports specifying a static visibility modifier for the scrollbars,
+ * similar to a {@link ScrollPane}'s bar policy.
+ *
+ * This class makes us of some internal, non-API JavaFX objects and methods and
+ * thus, might break once the codebase moves to a newer version.
+ *
+ * @author Alexandre Montplaisir
+ */
+@SuppressWarnings("restriction")
+public class TimeGraphWidgetTreeArea extends BorderPane {
+
+ private final double fEntryHeight;
+
+ private final TreeAreaTreeView fTreeView;
+
+ /**
+ * Constructor
+ */
+ public TimeGraphWidgetTreeArea(double entryHeight, ObjectProperty<@Nullable ITmfTrace> targetTraceProperty) {
+ TreeItem<String> treeRoot = new TreeItem<>();
+ treeRoot.setExpanded(true);
+
+ TreeAreaTreeView treeView = new TreeAreaTreeView(treeRoot);
+ treeView.setFixedCellSize(entryHeight);
+
+ /*
+ * Instead of using the TreeView's scrollbar, whose visibility and size are
+ * managed internally by the super-class, we will instantiate our own and will
+ * bind its important properties to the "real" one.
+ */
+ ScrollBar dummyScrollBar = new ScrollBar();
+ dummyScrollBar.setOrientation(Orientation.HORIZONTAL);
+ ScrollBar realHScrollBar = treeView.getHBar();
+ dummyScrollBar.valueProperty().bindBidirectional(realHScrollBar.valueProperty());
+ dummyScrollBar.visibleAmountProperty().bind(treeView.widthProperty().divide(2));
+
+ /* Add the "children" to this BorderPane */
+ setCenter(treeView);
+ setBottom(dummyScrollBar);
+
+ treeView.setCellFactory(view -> new TreeAreaTreeViewCell());
+
+ fEntryHeight = entryHeight;
+ fTreeView = treeView;
+ }
+
+ public void clear() {
+ fTreeView.setRoot(null);
+ }
+
+ public ScrollBar getVerticalScrollBar() {
+ return fTreeView.getVBar();
+ }
+
+ public DoubleBinding currentHeightProperty() {
+ return fTreeView.expandedItemCountProperty().multiply((fEntryHeight));
+ }
+
+ public void updateTreeContents(TimeGraphTreeRender treeRender) {
+ TreeItem<String> newRoot = getTreeItemForElement(treeRender.getRootElement());
+ newRoot.setExpanded(true);
+ Platform.runLater(() -> {
+ fTreeView.setRoot(newRoot);
+ });
+ }
+
+ private static TreeItem<String> getTreeItemForElement(TimeGraphTreeElement element) {
+ TreeItem<String> treeItem = new TreeItem<>(element.getName());
+ List<TreeItem<String>> childItems = element.getChildElements().stream()
+ .map(treeElem -> getTreeItemForElement(treeElem))
+ .collect(Collectors.toList());
+ if (!childItems.isEmpty()) {
+ treeItem.getChildren().addAll(childItems);
+ }
+
+ // TODO Correctly manage sub-trees being expanded. Disallow it for now.
+ treeItem.setExpanded(true);
+ treeItem.addEventHandler(TreeItem.branchCollapsedEvent(),
+ event -> event.getTreeItem().setExpanded(true));
+
+ return treeItem;
+ }
+
+ // ------------------------------------------------------------------------
+ // Methods related to the Tree area
+ // ------------------------------------------------------------------------
+
+ private static List<TreeItem<String>> getTreeItemNames(TimeGraphTreeRender treeRender) {
+ return treeRender.getAllTreeElements().stream()
+ .map(elem -> new TreeItem<>(elem.getName()))
+ .collect(Collectors.toList());
+ }
+
+ // TODO Background could be re-added by salvaging this
+// private static Node prepareTreeContents(TimeGraphTreeRender treeRender, ReadOnlyDoubleProperty widthProperty) {
+// /* Prepare the tree element objects */
+// List<Label> treeElements = treeRender.getAllTreeElements().stream()
+// // TODO Put as a real tree. TreeView ?
+// .map(elem -> new Label(elem.getName()))
+// .peek(label -> {
+// label.setPrefHeight(ENTRY_HEIGHT);
+// label.setPadding(new Insets(0, LABEL_SIDE_MARGIN, 0, LABEL_SIDE_MARGIN));
+// /*
+// * Re-set the solid background for the labels, so we do not
+// * see the background lines through.
+// */
+// label.setStyle(BACKGROUND_STYLE);
+// })
+// .collect(Collectors.toList());
+//
+// VBox treeElemsBox = new VBox(); // Change to TreeView eventually ?
+// treeElemsBox.getChildren().addAll(treeElements);
+//
+// /* Prepare the background layer with the horizontal alignment lines */
+// List<Line> lines = DoubleStream.iterate((ENTRY_HEIGHT / 2), y -> y + ENTRY_HEIGHT)
+// .limit(treeElements.size())
+// .mapToObj(y -> {
+// Line line = new Line();
+// line.startXProperty().bind(JfxUtils.ZERO_PROPERTY);
+// line.endXProperty().bind(widthProperty);
+// line.setStartY(y);
+// line.setEndY(y);
+//
+// line.setStroke(BACKGROUD_LINES_COLOR);
+// line.setStrokeWidth(1.0);
+// return line;
+// })
+// .collect(Collectors.toList());
+// Pane background = new Pane();
+// background.getChildren().addAll(lines);
+//
+// /* Put the background layer and the Tree View into their containers */
+// StackPane stackPane = new StackPane(background, treeElemsBox);
+// stackPane.setStyle(BACKGROUND_STYLE);
+// return stackPane;
+// }
+
+
+ // ------------------------------------------------------------------------
+ // Helper inner classes
+ // ------------------------------------------------------------------------
+
+ private static class TreeAreaTreeViewCell extends TreeCell<String> {
+
+ public TreeAreaTreeViewCell() {
+ // TODO Font size etc. could be managed here. Might be needed on ENTRY_HEIGHT
+ // becomes variable
+ setPadding(Insets.EMPTY);
+ }
+
+ @Override
+ public void updateItem(String item, boolean empty) {
+ super.updateItem(item, empty);
+ setGraphic(null);
+ if (empty) {
+ setText(null);
+ } else {
+ setText(item);
+ }
+ }
+ }
+
+ private static class TreeAreaTreeView extends TreeView<String> {
+
+ private TreeAreaTreeViewSkin fSkin = new TreeAreaTreeViewSkin(this);
+
+ public TreeAreaTreeView(TreeItem<String> root) {
+ super(root);
+
+ /*
+ * Completely hide both scrollbars. This works much better than trying to set
+ * their 'visible' property, which are constantly being changed by the
+ * superclass.
+ */
+ getHBar().minHeightProperty().bind(JfxUtils.ZERO_PROPERTY);
+ getHBar().prefHeightProperty().bind(JfxUtils.ZERO_PROPERTY);
+ getHBar().maxHeightProperty().bind(JfxUtils.ZERO_PROPERTY);
+
+ getVBar().minWidthProperty().bind(JfxUtils.ZERO_PROPERTY);
+ getVBar().prefWidthProperty().bind(JfxUtils.ZERO_PROPERTY);
+ getVBar().maxWidthProperty().bind(JfxUtils.ZERO_PROPERTY);
+
+ lookupAll(".scroll-bar").forEach(node -> node.setStyle("-fx-base: transparent;")); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ @Override
+ protected TreeAreaTreeViewSkin createDefaultSkin() {
+ return fSkin;
+ }
+
+ private TreeAreaTreeViewSkin retrieveSkin() {
+ return fSkin;
+ }
+
+ /**
+ * Retrieve the horizontal scrollbar
+ *
+ * @return The horizontal scrollbar
+ */
+ public ScrollBar getHBar() {
+ return retrieveSkin().getFlow().retrieveHbar();
+ }
+
+ /**
+ * Retrieve the vertical scrollbar
+ *
+ * @return The vertical scrollbar
+ */
+ public ScrollBar getVBar() {
+ return retrieveSkin().getFlow().retrieveVbar();
+ }
+ }
+
+ private static class TreeAreaTreeViewSkin extends TreeViewSkin<String> {
+
+ public TreeAreaTreeViewSkin(TreeAreaTreeView treeView) {
+ super(treeView);
+ }
+
+ @Override
+ protected TreeAreaVirtualFlow createVirtualFlow() {
+ return new TreeAreaVirtualFlow();
+ }
+
+ public TreeAreaVirtualFlow getFlow() {
+ return (TreeAreaVirtualFlow) requireNonNull(flow);
+ }
+
+ }
+
+ @SuppressWarnings("rawtypes")
+ private static class TreeAreaVirtualFlow extends VirtualFlow {
+
+ public ScrollBar retrieveHbar() {
+ return requireNonNull(getHbar());
+ }
+
+ public ScrollBar retrieveVbar() {
+ return requireNonNull(getVbar());
+ }
+
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph;
+
+import java.util.Objects;
+
+import org.eclipse.jdt.annotation.Nullable;
+
+import com.google.common.base.MoreObjects;
+
+public class VerticalPosition {
+
+ /**
+ * Placeholder for uninitialized vertical positions.
+ */
+ public static final VerticalPosition UNINITIALIZED_VP = new VerticalPosition(0.0, 0.0);
+
+ private static final double EPSILON = 0.00001;
+
+ public final double fTopPos;
+ public final double fBottomPos;
+
+ public VerticalPosition(double topPos, double bottomPos) {
+ fTopPos = topPos;
+ fBottomPos = bottomPos;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(fTopPos, fBottomPos);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ VerticalPosition other = (VerticalPosition) obj;
+ return (doubleEquals(fTopPos, other.fTopPos)
+ && doubleEquals(fBottomPos, other.fBottomPos));
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("fTopPos", fTopPos) //$NON-NLS-1$
+ .add("fBottomPos", fBottomPos) //$NON-NLS-1$
+ .toString();
+ }
+
+ private static boolean doubleEquals(double d1, double d2) {
+ return (Math.abs(d1 - d2) < EPSILON);
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.layer;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.FutureTask;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.lttng.scope.tmf2.views.core.TimeRange;
+import org.lttng.scope.tmf2.views.core.timegraph.model.provider.arrows.ITimeGraphModelArrowProvider;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.ColorDefinition;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.arrows.TimeGraphArrowRender;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.tree.TimeGraphTreeElement;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.tree.TimeGraphTreeRender;
+import org.lttng.scope.tmf2.views.ui.jfx.Arrow;
+import org.lttng.scope.tmf2.views.ui.jfx.JfxColorFactory;
+import org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.TimeGraphWidget;
+import org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.VerticalPosition;
+
+import com.google.common.collect.ImmutableMap;
+
+import javafx.application.Platform;
+import javafx.scene.Group;
+import javafx.scene.paint.Paint;
+
+public class TimeGraphArrowLayer extends TimeGraphLayer {
+
+ private final Map<ITimeGraphModelArrowProvider, ArrowConfig> fArrowProvidersConfig;
+
+ public TimeGraphArrowLayer(TimeGraphWidget widget, Group parentGroup) {
+ super(widget, parentGroup);
+
+ Collection<ITimeGraphModelArrowProvider> arrowProviders =
+ widget.getControl().getModelRenderProvider().getArrowProviders();
+
+ fArrowProvidersConfig = arrowProviders.stream()
+ .collect(ImmutableMap.toImmutableMap(
+ Function.identity(),
+ ap -> {
+ Group group = new Group();
+ ColorDefinition colorDef = ap.getArrowSeries().getColor();
+ Paint stroke = JfxColorFactory.getColorFromDef(colorDef);
+ return new ArrowConfig(group, stroke);
+ }));
+
+ fArrowProvidersConfig.values().stream()
+ .map(ArrowConfig::getGroup)
+ .forEach(parentGroup.getChildren()::add);
+
+ /*
+ * Add listeners to the registered arrow providers. If providers become
+ * enabled or disabled, we must repaint or hide the arrows from this
+ * provider's series.
+ */
+ arrowProviders.forEach(ap -> {
+ ap.enabledProperty().addListener((obs, oldValue, newValue) -> {
+ if (newValue) {
+ /*
+ * The provider is now enabled, we must fetch and display
+ * its arrows
+ */
+ TimeRange timeRange = getWidget().getViewContext().getCurrentVisibleTimeRange();
+ TimeGraphTreeRender treeRender = getWidget().getLatestTreeRender();
+ // FIXME Not using a task here, so this might end up running
+ // on the UI thread and not being cancellable...
+ paintArrowsOfProvider(treeRender, timeRange, ap, null);
+ } else {
+ /*
+ * The provider is now disabled, we must remove the existing
+ * arrows from this provider.
+ */
+ ArrowConfig config = fArrowProvidersConfig.get(ap);
+ if (config == null) {
+ return;
+ }
+ Platform.runLater(() -> {
+ config.getGroup().getChildren().clear();
+ });
+ }
+ });
+ });
+ }
+
+ @Override
+ public void drawContents(TimeGraphTreeRender treeRender, TimeRange timeRange,
+ VerticalPosition vPos, @Nullable FutureTask<?> task) {
+ fArrowProvidersConfig.keySet().stream()
+ .filter(arrowProvider -> arrowProvider.enabledProperty().get())
+ .forEach(arrowProvider -> paintArrowsOfProvider(treeRender, timeRange, arrowProvider, task));
+ }
+
+ @Override
+ public void clear() {
+ /*
+ * Only clear the children's children, not our direct children which
+ * could still be valid.
+ */
+ fArrowProvidersConfig.values().stream()
+ .map(ArrowConfig::getGroup)
+ .forEach(group -> group.getChildren().clear());
+ }
+
+ private void paintArrowsOfProvider(TimeGraphTreeRender treeRender, TimeRange timeRange,
+ ITimeGraphModelArrowProvider arrowProvider, @Nullable FutureTask<?> task) {
+ ArrowConfig config = fArrowProvidersConfig.get(arrowProvider);
+ if (config == null) {
+ /* Should not happen... */
+ return;
+ }
+
+ TimeGraphArrowRender arrowRender = arrowProvider.getArrowRender(treeRender, timeRange, task);
+ Collection<Arrow> arrows = prepareArrows(treeRender, arrowRender, config.getStroke());
+
+ Platform.runLater(() -> {
+ config.getGroup().getChildren().clear();
+ config.getGroup().getChildren().addAll(arrows);
+ });
+ }
+
+ private Collection<Arrow> prepareArrows(TimeGraphTreeRender treeRender,
+ TimeGraphArrowRender arrowRender, Paint arrowStroke) {
+ final double entryHeight = TimeGraphWidget.ENTRY_HEIGHT;
+
+ Collection<Arrow> arrows = arrowRender.getArrows().stream()
+ .map(timeGraphArrow -> {
+ TimeGraphTreeElement startTreeElem = timeGraphArrow.getStartEvent().getTreeElement();
+ TimeGraphTreeElement endTreeElem = timeGraphArrow.getEndEvent().getTreeElement();
+ long startTimestamp = timeGraphArrow.getStartEvent().getTimestamp();
+ long endTimestamp = timeGraphArrow.getEndEvent().getTimestamp();
+ // FIXME Build and use a hashmap instead for indexes
+ int startIndex = treeRender.getAllTreeElements().indexOf(startTreeElem);
+ int endIndex = treeRender.getAllTreeElements().indexOf(endTreeElem);
+ if (startIndex == -1 || endIndex == -1) {
+ /* We shouldn't have received this... */
+ return null;
+ }
+
+ double startX = getWidget().timestampToPaneXPos(startTimestamp);
+ double endX = getWidget().timestampToPaneXPos(endTimestamp);
+ double startY = startIndex * entryHeight + entryHeight / 2;
+ double endY = endIndex * entryHeight + entryHeight / 2;
+
+ Arrow arrow = new Arrow(startX, startY, endX, endY);
+ arrow.setStroke(arrowStroke);
+ return arrow;
+ })
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList());
+ return arrows;
+ }
+
+ public synchronized Collection<Arrow> getRenderedArrows() {
+ /*
+ * Retrieve the rendered arrows of each group, and flatten them into a
+ * single collection.
+ */
+ return fArrowProvidersConfig.values().stream()
+ .map(ArrowConfig::getGroup)
+ .map(Group::getChildren)
+ .flatMap(Collection::stream)
+ .map(node -> (Arrow) node)
+ .collect(Collectors.toList());
+ }
+
+ private static class ArrowConfig {
+
+ private final Group fGroup;
+ private final Paint fStroke;
+
+ public ArrowConfig(Group group, Paint stroke) {
+ fGroup = group;
+ fStroke = stroke;
+ }
+
+ public Group getGroup() {
+ return fGroup;
+ }
+
+ public Paint getStroke() {
+ return fStroke;
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.layer;
+
+import java.util.LinkedList;
+import java.util.concurrent.FutureTask;
+import java.util.stream.DoubleStream;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.lttng.scope.tmf2.views.core.TimeRange;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.tree.TimeGraphTreeRender;
+import org.lttng.scope.tmf2.views.ui.jfx.JfxUtils;
+import org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.TimeGraphWidget;
+import org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.VerticalPosition;
+
+import javafx.application.Platform;
+import javafx.scene.Group;
+import javafx.scene.shape.Line;
+
+/**
+ * Sub-control of the time graph widget taking care of simply drawing the
+ * background lines in the area of the current visible window.
+ *
+ * @author Alexandre Montplaisir
+ */
+public class TimeGraphBackgroundLayer extends TimeGraphLayer {
+
+ /**
+ * Constructor
+ *
+ * @param widget
+ * Main widget which this control supports
+ * @param parentGroup
+ * The group to which this layer should add its children
+ */
+ public TimeGraphBackgroundLayer(TimeGraphWidget widget, Group parentGroup) {
+ super(widget, parentGroup);
+ }
+
+ @Override
+ public void drawContents(TimeGraphTreeRender treeRender, TimeRange timeRange,
+ VerticalPosition vPos, @Nullable FutureTask<?> task) {
+ final double entryHeight = TimeGraphWidget.ENTRY_HEIGHT;
+
+ final int entriesToPrefetch = getWidget().getDebugOptions().entryPadding.get();
+ int totalNbEntries = treeRender.getAllTreeElements().size();
+
+ final double timeGraphWidth = getWidget().getTimeGraphPane().getWidth();
+ final double paintTopPos = Math.max(0.0, vPos.fTopPos - entriesToPrefetch * entryHeight);
+ final double paintBottomPos = Math.min(vPos.fBottomPos + entriesToPrefetch * entryHeight,
+ /*
+ * If there are less tree elements than can fill the window,
+ * stop at the end of the real tree elements.
+ */
+ totalNbEntries * entryHeight);
+
+ LinkedList<Line> lines = new LinkedList<>();
+ DoubleStream.iterate((entryHeight / 2), y -> y + entryHeight)
+ // TODO Java 9 will allow using dropWhile()/takeWhile()/collect
+ .filter(y -> y > paintTopPos)
+ .peek(y -> {
+ Line line = new Line(0, y, timeGraphWidth, y);
+ line.setStroke(TimeGraphWidget.BACKGROUD_LINES_COLOR);
+ line.setStrokeWidth(1.0);
+
+ lines.add(line);
+ })
+ .allMatch(y -> y < paintBottomPos);
+ // The list contains the first element that didn't match the predicate,
+ // we don't want it.
+ if (!lines.isEmpty()) {
+ lines.removeLast();
+ }
+
+ Platform.runLater(() -> {
+ getParentGroup().getChildren().clear();
+ getParentGroup().getChildren().addAll(lines);
+ });
+ }
+
+ @Override
+ public void clear() {
+ JfxUtils.runOnMainThread(() -> getParentGroup().getChildren().clear());
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.layer;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.FutureTask;
+import java.util.stream.Collectors;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.lttng.scope.tmf2.views.core.TimeRange;
+import org.lttng.scope.tmf2.views.core.timegraph.model.provider.drawnevents.ITimeGraphDrawnEventProvider;
+import org.lttng.scope.tmf2.views.core.timegraph.model.provider.drawnevents.TimeGraphDrawnEventProviderManager;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.TimeGraphEvent;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.drawnevents.TimeGraphDrawnEvent;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.drawnevents.TimeGraphDrawnEventRender;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.drawnevents.TimeGraphDrawnEventSeries.SymbolStyle;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.tree.TimeGraphTreeRender;
+import org.lttng.scope.tmf2.views.ui.jfx.JfxColorFactory;
+import org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.TimeGraphWidget;
+import org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.VerticalPosition;
+
+import javafx.application.Platform;
+import javafx.collections.ObservableSet;
+import javafx.collections.SetChangeListener;
+import javafx.scene.Group;
+import javafx.scene.paint.Color;
+import javafx.scene.shape.Circle;
+import javafx.scene.shape.Polygon;
+import javafx.scene.shape.Rectangle;
+import javafx.scene.shape.SVGPath;
+import javafx.scene.shape.Shape;
+
+public class TimeGraphDrawnEventLayer extends TimeGraphLayer {
+
+ private final Map<ITimeGraphDrawnEventProvider, Group> fEventProviders = new HashMap<>();
+
+ public TimeGraphDrawnEventLayer(TimeGraphWidget widget, Group parentGroup) {
+ super(widget, parentGroup);
+
+ ObservableSet<ITimeGraphDrawnEventProvider> providers = TimeGraphDrawnEventProviderManager.instance().getRegisteredProviders();
+ /* Populate with the initial values */
+ providers.forEach(this::trackEventProvider);
+
+ /* Add listeners to track registered/deregistered providers */
+ providers.addListener((SetChangeListener<ITimeGraphDrawnEventProvider>) change -> {
+ if (change == null) {
+ return;
+ }
+ ITimeGraphDrawnEventProvider addedProvider = change.getElementAdded();
+ if (addedProvider != null) {
+ trackEventProvider(addedProvider);
+ }
+
+ ITimeGraphDrawnEventProvider removedProvider = change.getElementRemoved();
+ if (removedProvider != null) {
+ untrackEventProvider(removedProvider);
+ }
+ });
+ }
+
+ private void trackEventProvider(ITimeGraphDrawnEventProvider provider) {
+ Group newGroup = new Group();
+ Group oldGroup = fEventProviders.put(provider, newGroup);
+ if (oldGroup == null) {
+ Platform.runLater(() -> {
+ getParentGroup().getChildren().add(newGroup);
+ });
+ } else {
+ /* Remove the old group in case there was already one. */
+ Platform.runLater(() -> {
+ getParentGroup().getChildren().remove(oldGroup);
+ getParentGroup().getChildren().add(newGroup);
+ });
+ }
+
+ /*
+ * Add a listener to this provider's "enabled" property, so that when it
+ * changes from enabled to disabled and vice versa, we update the view
+ * accordingly.
+ */
+ provider.enabledProperty().addListener((obs, oldValue, newValue) -> {
+ if (newValue) {
+ /* The provider was just enabled */
+ TimeRange timeRange = getWidget().getViewContext().getCurrentVisibleTimeRange();
+ TimeGraphTreeRender treeRender = getWidget().getLatestTreeRender();
+ // FIXME Use a Task?
+ paintEventsOfProvider(treeRender, timeRange, provider, null);
+ } else {
+ /* Provider was disabled. Clear the children of its group. */
+ Group group = fEventProviders.get(provider);
+ if (group == null) {
+ return;
+ }
+ Platform.runLater(() -> group.getChildren().clear());
+ }
+ });
+
+ }
+
+ private void untrackEventProvider(ITimeGraphDrawnEventProvider provider) {
+ Group group = fEventProviders.remove(provider);
+ if (group != null) {
+ Platform.runLater(() -> {
+ getParentGroup().getChildren().remove(group);
+ });
+ }
+ /*
+ * Note, we do not need to explicitly remove the ChangeListener we added
+ * above, the weak reference will simply lose it at some point.
+ */
+ }
+
+ @Override
+ public void drawContents(TimeGraphTreeRender treeRender, TimeRange timeRange,
+ VerticalPosition vPos, @Nullable FutureTask<?> task) {
+ fEventProviders.keySet().stream()
+ .filter(provider -> provider.enabledProperty().get())
+ .forEach(provider -> paintEventsOfProvider(treeRender, timeRange, provider, task));
+ }
+
+ @Override
+ public void clear() {
+ fEventProviders.values().forEach(group -> group.getChildren().clear());
+ }
+
+ private void paintEventsOfProvider(TimeGraphTreeRender treeRender, TimeRange timeRange,
+ ITimeGraphDrawnEventProvider eventsProvider, @Nullable FutureTask<?> task) {
+
+ TimeGraphDrawnEventRender eventRender = eventsProvider.getEventRender(treeRender, timeRange, task);
+ Collection<Shape> drawnEvents = prepareDrawnEvents(treeRender, eventRender);
+
+ Group paintGroup = requireNonNull(fEventProviders.get(eventsProvider));
+ Platform.runLater(() -> {
+ paintGroup.getChildren().clear();
+ paintGroup.getChildren().addAll(drawnEvents);
+ });
+ }
+
+ private Collection<Shape> prepareDrawnEvents(TimeGraphTreeRender treeRender, TimeGraphDrawnEventRender eventRender) {
+ final double entryHeight = TimeGraphWidget.ENTRY_HEIGHT;
+
+ Collection<Shape> shapes = eventRender.getEvents().stream()
+ .map(event -> {
+ TimeGraphEvent tgEvent = event.getEvent();
+ double x = getWidget().timestampToPaneXPos(tgEvent.getTimestamp());
+
+ int treeIndex = treeRender.getAllTreeElements().indexOf(tgEvent.getTreeElement());
+ if (treeIndex == -1) {
+ return null;
+ }
+ double y = treeIndex * entryHeight + entryHeight / 2;
+
+ Shape shape = getShapeFromEvent(event);
+ /*
+ * Some symbols already use the layout* properties for
+ * adjusting their center. Use translate* properties for
+ * their positioning on the timegraph.
+ */
+ shape.setTranslateX(x);
+ shape.setTranslateY(y);
+ return shape;
+ })
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList());
+
+ return shapes;
+ }
+
+ private static Shape getShapeFromEvent(TimeGraphDrawnEvent event) {
+ Color color = JfxColorFactory.getColorFromDef(event.getEventSeries().getColor().get());
+ SymbolStyle symbol = event.getEventSeries().getSymbolStyle().get();
+ Shape shape = getShapeFromSymbol(symbol);
+ shape.setFill(color);
+ return shape;
+ }
+
+ public static Shape getShapeFromSymbol(SymbolStyle symbol) {
+ Shape shape;
+ switch (symbol) {
+ case CIRCLE:
+ shape = new Circle(5);
+ break;
+
+ case DIAMOND: {
+ shape = new Polygon(5.0, 0.0,
+ 10.0, 5.0,
+ 5.0, 10.0,
+ 0.0, 5.0);
+ shape.relocate(-5.0, -5.0);
+ }
+ break;
+
+ case SQUARE:
+ shape = new Rectangle(-5, -5, 10, 10);
+ break;
+
+ case STAR:
+ // FIXME bigger?
+ shape = new Polygon(4.0, 0.0,
+ 5.0, 4.0,
+ 8.0, 4.0,
+ 6.0, 6.0,
+ 7.0, 9.0,
+ 4.0, 7.0,
+ 1.0, 9.0,
+ 2.0, 6.0,
+ 0.0, 4.0,
+ 3.0, 4.0);
+ shape.relocate(-4, -4.5);
+ break;
+
+ case TRIANGLE: {
+ SVGPath path = new SVGPath();
+ path.setContent("M5,0 L10,8 L0,8 Z"); //$NON-NLS-1$
+ path.relocate(-5, -2);
+ shape = path;
+ }
+ break;
+
+ case CROSS:
+ default: {
+ SVGPath path = new SVGPath();
+ path.setContent("M2,0 L5,4 L8,0 L10,0 L10,2 L6,5 L10,8 L10,10 L8,10 L5,6 L2, 10 L0,10 L0,8 L4,5 L0,2 L0,0 Z"); //$NON-NLS-1$
+ path.relocate(-5, -5);
+ shape = path;
+ }
+ break;
+
+ }
+
+ shape.setStroke(Color.BLACK);
+ return shape;
+ }
+
+ public synchronized Collection<Shape> getRenderedEvents() {
+ /*
+ * Retrieve the rendered events of each group, and flatten them into a
+ * single collection.
+ */
+ return fEventProviders.values().stream()
+ .map(Group::getChildren)
+ .flatMap(Collection::stream)
+ .map(node -> (Shape) node)
+ .collect(Collectors.toList());
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.layer;
+
+import java.util.concurrent.FutureTask;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.lttng.scope.tmf2.views.core.TimeRange;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.tree.TimeGraphTreeRender;
+import org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.TimeGraphWidget;
+import org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.VerticalPosition;
+
+import javafx.scene.Group;
+
+/**
+ * Base class for layers of the timegraph, providing one particular data aspect.
+ *
+ * @author Alexandre Montplaisir
+ */
+public abstract class TimeGraphLayer {
+
+ private final TimeGraphWidget fWidget;
+ private final Group fParentGroup;
+
+ /**
+ * Constructor
+ *
+ * @param widget
+ * The widget to which this layer belongs
+ * @param parentGroup
+ * The group to which this layer should add its children
+ */
+ public TimeGraphLayer(TimeGraphWidget widget, Group parentGroup) {
+ fWidget = widget;
+ fParentGroup = parentGroup;
+ }
+
+ /**
+ * Get the layer's widget.
+ *
+ * @return The widget
+ */
+ protected TimeGraphWidget getWidget() {
+ return fWidget;
+ }
+
+ /**
+ * Get this layer's parent group, in which all its generated children should
+ * be added.
+ *
+ * @return The parent group
+ */
+ public Group getParentGroup() {
+ return fParentGroup;
+ }
+
+ /**
+ * Ask the layer to populate its scenegraph. This is usually done by having
+ * the Layer add children Nodes to itself.
+ *
+ * @param treeRender
+ * The current tree render of the time graph. Can be used to get
+ * the number of visible entries, etc.
+ * @param timeRange
+ * The time range, or "horizontal position" of the current
+ * visible window
+ * @param vPos
+ * The vertical position of the current visible window
+ * @param task
+ * An optional Task which is calling this method. Long-running
+ * implementations are encouraged to check the 'cancelled' status
+ * of this Task periodically (if supplied), and early exit in
+ * case of cancellation.
+ */
+ public abstract void drawContents(TimeGraphTreeRender treeRender, TimeRange timeRange,
+ VerticalPosition vPos, @Nullable FutureTask<?> task);
+
+ /**
+ * Remove from the scenegraph the Nodes generated by this layer.
+ */
+ public abstract void clear();
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.layer;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.concurrent.FutureTask;
+import java.util.function.Predicate;
+import java.util.stream.Stream;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.lttng.scope.tmf2.views.core.TimeRange;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.tree.TimeGraphTreeRender;
+import org.lttng.scope.tmf2.views.ui.jfx.JfxUtils;
+import org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.TimeGraphWidget;
+import org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.VerticalPosition;
+
+import javafx.event.EventHandler;
+import javafx.scene.Group;
+import javafx.scene.input.MouseButton;
+import javafx.scene.input.MouseEvent;
+import javafx.scene.layout.Pane;
+import javafx.scene.paint.Color;
+import javafx.scene.shape.Rectangle;
+import javafx.scene.shape.StrokeLineCap;
+
+/**
+ * Sub-control of the time graph widget to handle the selection layer, which
+ * displays the current and ongoing selections.
+ *
+ * It also sends the corresponding selection update whenever a new selection is
+ * made.
+ *
+ * @author Alexandre Montplaisir
+ */
+public class TimeGraphSelectionLayer extends TimeGraphLayer {
+
+ /* Style settings. TODO Move to debug options? */
+ private static final double SELECTION_STROKE_WIDTH = 1;
+ private static final Color SELECTION_STROKE_COLOR = requireNonNull(Color.BLUE);
+ private static final Color SELECTION_FILL_COLOR = requireNonNull(Color.LIGHTBLUE.deriveColor(0, 1.2, 1, 0.4));
+
+ /**
+ * These events are to be ignored by the time graph pane, they should
+ * "bubble up" to the scrollpane to be used for panning.
+ */
+ private static final Predicate<MouseEvent> MOUSE_EVENT_IGNORED = e -> {
+ return (e.getButton() == MouseButton.SECONDARY
+ || e.getButton() == MouseButton.MIDDLE
+ || e.isControlDown());
+ };
+
+ private final Rectangle fSelectionRect = new Rectangle();
+ private final Rectangle fOngoingSelectionRect = new Rectangle();
+
+ private final SelectionContext fSelectionCtx = new SelectionContext();
+
+ /**
+ * Constructor
+ *
+ * @param widget
+ * The corresponding time graph widget
+ * @param parentGroup
+ * The group to which this layer should add its children
+ */
+ public TimeGraphSelectionLayer(TimeGraphWidget widget, Group parentGroup) {
+ super(widget, parentGroup);
+
+ final Pane timeGraphPane = getWidget().getTimeGraphPane();
+
+ fSelectionRect.setStroke(SELECTION_STROKE_COLOR);
+ fSelectionRect.setStrokeWidth(SELECTION_STROKE_WIDTH);
+ fSelectionRect.setStrokeLineCap(StrokeLineCap.ROUND);
+
+ Stream.of(fSelectionRect, fOngoingSelectionRect).forEach(rect -> {
+ rect.setMouseTransparent(true);
+ rect.setFill(SELECTION_FILL_COLOR);
+
+ /*
+ * We keep the 'x' property at 0, and we'll use 'layoutX' to set the
+ * start position of the rectangles.
+ *
+ * See https://github.com/lttng/lttng-scope/issues/25
+ */
+ rect.xProperty().bind(JfxUtils.ZERO_PROPERTY);
+ rect.yProperty().bind(JfxUtils.ZERO_PROPERTY);
+ rect.heightProperty().bind(timeGraphPane.heightProperty());
+ });
+
+ /*
+ * Note, unlike most other controls, we will not add/remove children to
+ * the target group, we will add them once then toggle their 'visible'
+ * property.
+ */
+ fSelectionRect.setVisible(true);
+ fOngoingSelectionRect.setVisible(false);
+ getParentGroup().getChildren().addAll(fSelectionRect, fOngoingSelectionRect);
+
+ timeGraphPane.addEventHandler(MouseEvent.MOUSE_PRESSED, fSelectionCtx.fMousePressedEventHandler);
+ timeGraphPane.addEventHandler(MouseEvent.MOUSE_DRAGGED, fSelectionCtx.fMouseDraggedEventHandler);
+ timeGraphPane.addEventHandler(MouseEvent.MOUSE_RELEASED, fSelectionCtx.fMouseReleasedEventHandler);
+ }
+
+ /**
+ * Get the rectangle object representing the current selection range.
+ *
+ * @return The current selection rectangle
+ */
+ public Rectangle getSelectionRectangle() {
+ return fSelectionRect;
+ }
+
+ /**
+ * Get the rectangle object representing the ongoing selection. It is
+ * displayed while the user holds the mouse down and drags to make a
+ * selection, but before the mouse is released. The "real" selection is only
+ * applied on mouse release.
+ *
+ * @return The ongoing selection rectangle
+ */
+ public Rectangle getOngoingSelectionRectangle() {
+ return fOngoingSelectionRect;
+ }
+
+ @Override
+ public void drawContents(TimeGraphTreeRender treeRender, TimeRange timeRange,
+ VerticalPosition vPos, @Nullable FutureTask<?> task) {
+ drawSelection(timeRange);
+ }
+
+ @Override
+ public void clear() {
+ /* We don't have to clear anything */
+ }
+
+ /**
+ * Draw a new "current" selection. For times where the selection is updated
+ * elsewhere in the framework.
+ *
+ * @param timeRange
+ * The time range of the new selection
+ */
+ public void drawSelection(TimeRange timeRange) {
+ double xStart = getWidget().timestampToPaneXPos(timeRange.getStart());
+ double xEnd = getWidget().timestampToPaneXPos(timeRange.getEnd());
+ double xWidth = xEnd - xStart;
+
+ fSelectionRect.setLayoutX(xStart);
+ fSelectionRect.setWidth(xWidth);
+
+ fSelectionRect.setVisible(true);
+ }
+
+ /**
+ * Class encapsulating the time range selection, related drawing and
+ * listeners.
+ */
+ private class SelectionContext {
+
+ private boolean fOngoingSelection;
+ private double fMouseOriginX;
+
+ public final EventHandler<MouseEvent> fMousePressedEventHandler = e -> {
+ if (MOUSE_EVENT_IGNORED.test(e)) {
+ return;
+ }
+ e.consume();
+
+ if (fOngoingSelection) {
+ return;
+ }
+
+ /* Remove the current selection, if there is one */
+ fSelectionRect.setVisible(false);
+
+ fMouseOriginX = e.getX();
+
+ fOngoingSelectionRect.setLayoutX(fMouseOriginX);
+ fOngoingSelectionRect.setWidth(0);
+ fOngoingSelectionRect.setVisible(true);
+
+ fOngoingSelection = true;
+ };
+
+ public final EventHandler<MouseEvent> fMouseDraggedEventHandler = e -> {
+ if (MOUSE_EVENT_IGNORED.test(e)) {
+ return;
+ }
+ e.consume();
+
+ double newX = e.getX();
+ double offsetX = newX - fMouseOriginX;
+
+ if (offsetX > 0) {
+ fOngoingSelectionRect.setLayoutX(fMouseOriginX);
+ fOngoingSelectionRect.setWidth(offsetX);
+ } else {
+ fOngoingSelectionRect.setLayoutX(newX);
+ fOngoingSelectionRect.setWidth(-offsetX);
+ }
+
+ };
+
+ public final EventHandler<MouseEvent> fMouseReleasedEventHandler = e -> {
+ if (MOUSE_EVENT_IGNORED.test(e)) {
+ return;
+ }
+ e.consume();
+
+ fOngoingSelectionRect.setVisible(false);
+
+ /* Send a time range selection signal for the currently selected time range */
+ double startX = Math.max(0, fOngoingSelectionRect.getLayoutX());
+ // FIXME Possible glitch when selecting backwards outside of the window
+ double endX = Math.min(getWidget().getTimeGraphPane().getWidth(), startX + fOngoingSelectionRect.getWidth());
+ long tsStart = getWidget().paneXPosToTimestamp(startX);
+ long tsEnd = getWidget().paneXPosToTimestamp(endX);
+
+ getWidget().getControl().updateTimeRangeSelection(TimeRange.of(tsStart, tsEnd));
+
+ fOngoingSelection = false;
+ };
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.layer;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.FutureTask;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.lttng.scope.tmf2.views.core.TimeRange;
+import org.lttng.scope.tmf2.views.core.timegraph.model.provider.states.ITimeGraphModelStateProvider;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.states.TimeGraphStateRender;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.tree.TimeGraphTreeElement;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.tree.TimeGraphTreeRender;
+import org.lttng.scope.tmf2.views.ui.jfx.JfxUtils;
+import org.lttng.scope.tmf2.views.ui.timeline.DebugOptions;
+import org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.StateRectangle;
+import org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.TimeGraphWidget;
+import org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.VerticalPosition;
+
+import javafx.application.Platform;
+import javafx.scene.Group;
+import javafx.scene.Node;
+import javafx.scene.control.OverrunStyle;
+import javafx.scene.paint.Color;
+import javafx.scene.text.Font;
+import javafx.scene.text.Text;
+
+/**
+ * Time graph layer taking care of drawing the state intervals and their labels.
+ *
+ * Intervals and labels are part of separate Groups, which the time graph widget
+ * can stack in the correct order. This ensures that the labels are always shown
+ * on top of the states.
+ *
+ * @author Alexandre Montplaisir
+ */
+public class TimeGraphStateLayer extends TimeGraphLayer {
+
+ private final Group fLabelGroup = new Group();
+ private final ITimeGraphModelStateProvider fStateProvider;
+
+ private TimeRange fWindowRange;
+
+ /**
+ * Constructor
+ *
+ * @param widget
+ * Time graph widget to which this layer belongs
+ * @param parentGroup
+ * The group to which this layer should add its children
+ */
+ public TimeGraphStateLayer(TimeGraphWidget widget, Group parentGroup) {
+ super(widget, parentGroup);
+
+ fStateProvider = widget.getControl().getModelRenderProvider().getStateProvider();
+ /*
+ * Initially we'll set our window range to the one from the view
+ * context, afterwards we'll let the widget update it.
+ */
+ fWindowRange = widget.getControl().getViewContext().getCurrentVisibleTimeRange();
+ }
+
+ /**
+ * Return the separate Group used to hold the state labels.
+ *
+ * @return The group of state labels
+ */
+ public Group getLabelGroup() {
+ return fLabelGroup;
+ }
+
+ /**
+ * Update the window range tracked by this layer. This will be used to
+ * "clamp" the state labels to the current visible window in case the state
+ * intervals are larger than it.
+ *
+ * It may or may not be the same as the current UI visible range.
+ *
+ * @param windowRange
+ * The new window range
+ */
+ public void setWindowRange(TimeRange windowRange) {
+ fWindowRange = windowRange;
+ }
+
+ @Override
+ public void drawContents(TimeGraphTreeRender treeRender, TimeRange timeRange,
+ VerticalPosition vPos, @Nullable FutureTask<?> task) {
+
+ final long resolution = Math.max(1, Math.round(getWidget().getCurrentNanosPerPixel()));
+ final List<TimeGraphTreeElement> allTreeElements = treeRender.getAllTreeElements();
+ final int nbElements = allTreeElements.size();
+ final int entriesToPrefetch = getWidget().getDebugOptions().entryPadding.get();
+ final int topEntry = Math.max(0,
+ TimeGraphWidget.paneYPosToEntryListIndex(vPos.fTopPos, TimeGraphWidget.ENTRY_HEIGHT) - entriesToPrefetch);
+ final int bottomEntry = Math.min(nbElements,
+ TimeGraphWidget.paneYPosToEntryListIndex(vPos.fBottomPos, TimeGraphWidget.ENTRY_HEIGHT) + entriesToPrefetch);
+
+ System.out.println("topEntry=" + topEntry +", bottomEntry=" + bottomEntry);
+
+ List<TimeGraphStateRender> stateRenders = allTreeElements.subList(topEntry, bottomEntry).stream()
+ .map(treeElem -> fStateProvider.getStateRender(treeElem, timeRange, resolution, task))
+ .collect(Collectors.toList());
+
+ if (task != null && task.isCancelled()) {
+ return;
+ }
+
+ Collection<StateRectangle> stateRectangles = prepareStateRectangles(stateRenders, topEntry);
+ Node statesLayerContents = prepareTimeGraphStatesContents(stateRectangles);
+ Node labelsLayerContents = prepareTimeGrahLabelsContents(stateRectangles, fWindowRange);
+
+ /*
+ * Go over all state rectangles, and bring the "multi-state"
+ * ones to the front, to be sure they show on top of the others.
+ * Note we cannot do the forEach() as part of the stream, that
+ * would throw a ConcurrentModificationException.
+ */
+ ((Group) statesLayerContents).getChildren().stream()
+ .map(node -> (StateRectangle) node)
+ .filter(rect -> (rect.getStateInterval().isMultiState()))
+ .collect(Collectors.toList())
+ .forEach(Node::toFront);
+
+ Platform.runLater(() -> {
+ getParentGroup().getChildren().clear();
+ getLabelGroup().getChildren().clear();
+
+ getParentGroup().getChildren().add(statesLayerContents);
+ getLabelGroup().getChildren().add(labelsLayerContents);
+ });
+ }
+
+ @Override
+ public void clear() {
+ JfxUtils.runOnMainThread(() -> {
+ getParentGroup().getChildren().clear();
+ getLabelGroup().getChildren().clear();
+ });
+ }
+
+ private Collection<StateRectangle> prepareStateRectangles(
+ List<TimeGraphStateRender> stateRenders, int topEntry) {
+ /* Prepare the colored state rectangles */
+ Collection<StateRectangle> rectangles = IntStream.range(0, stateRenders.size()).parallel()
+ .mapToObj(idx -> getRectanglesForStateRender(stateRenders.get(idx), idx + topEntry))
+ .flatMap(Function.identity())
+ .collect(Collectors.toSet());
+ return rectangles;
+ }
+
+ private Stream<StateRectangle> getRectanglesForStateRender(TimeGraphStateRender stateRender, int entryIndex) {
+ return stateRender.getStateIntervals().stream()
+ .map(interval -> new StateRectangle(getWidget(), interval, entryIndex));
+ }
+
+ private static Node prepareTimeGraphStatesContents(Collection<StateRectangle> stateRectangles) {
+ Group group = new Group();
+ group.getChildren().addAll(stateRectangles);
+ return group;
+ }
+
+ private Node prepareTimeGrahLabelsContents(Collection<StateRectangle> stateRectangles,
+ TimeRange windowRange) {
+ double minX = getWidget().timestampToPaneXPos(windowRange.getStart());
+
+ final String ellipsisStr = DebugOptions.ELLIPSIS_STRING;
+ final double ellipsisWidth = getWidget().getDebugOptions().getEllipsisWidth();
+ final Font textFont = getWidget().getDebugOptions().stateLabelFont.get();
+ final OverrunStyle overrunStyle = OverrunStyle.ELLIPSIS;
+ final Color textColor = Color.WHITE;
+
+ /* Requires a ~2 pixels adjustment to be centered on the states */
+ final double yOffset = TimeGraphWidget.ENTRY_HEIGHT / 2.0 + 2.0;
+ Collection<Node> texts = stateRectangles.stream()
+ /* Only try to annotate rectangles that are large enough */
+ .filter(stateRect -> stateRect.getWidth() > ellipsisWidth)
+ .filter(stateRect -> stateRect.getStateInterval().getLabel() != null)
+ .map(stateRect -> {
+ String labelText = requireNonNull(stateRect.getStateInterval().getLabel());
+ /* A small offset looks better here */
+ double textX = Math.max(minX, stateRect.getX()) + 4.0;
+ double textY = stateRect.getY() + yOffset;
+
+ double rectEndX = stateRect.getX() + stateRect.getWidth();
+ double minWidth = rectEndX - textX;
+
+ String ellipsedText = JfxUtils.computeClippedText(textFont,
+ labelText,
+ minWidth,
+ overrunStyle,
+ ellipsisStr);
+
+ if (ellipsedText.equals(ellipsisStr)) {
+ return null;
+ }
+
+ Text text = new Text(textX, textY, ellipsedText);
+ text.setFont(textFont);
+ text.setFill(textColor);
+ return text;
+ })
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList());
+
+ return new Group(texts);
+ }
+
+ /**
+ * Retrieve the state rectangles currently present in the scenegraph. This
+ * should include all currently visible ones, but also possibly more (due to
+ * padding, prefetching, etc.)
+ *
+ * @return The state rectangles
+ */
+ public Collection<StateRectangle> getRenderedStateRectangles() {
+ if (getParentGroup().getChildren().isEmpty()) {
+ return Collections.EMPTY_LIST;
+ }
+ Collection<?> stateRectangles = ((Group) getParentGroup().getChildren().get(0)).getChildren();
+ @SuppressWarnings("unchecked")
+ Collection<StateRectangle> ret = (@NonNull Collection<StateRectangle>) stateRectangles;
+ return ret;
+ }
+
+ /**
+ * Retrieve the state labels current drawn by this layer.
+ *
+ * @return The state labels
+ */
+ public Collection<Text> getRenderedStateLabels() {
+ if (fLabelGroup.getChildren().isEmpty()) {
+ return Collections.EMPTY_LIST;
+ }
+ /* The labels are wrapped in a group */
+ Collection<?> texts = ((Group) fLabelGroup.getChildren().get(0)).getChildren();
+ @SuppressWarnings("unchecked")
+ Collection<Text> ret = (@NonNull Collection<Text>) texts;
+ return ret;
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+@org.eclipse.jdt.annotation.NonNullByDefault
+package org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.layer;
--- /dev/null
+###############################################################################
+# Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Eclipse Public License v1.0
+# which accompanies this distribution, and is available at
+# http://www.eclipse.org/legal/epl-v10.html
+###############################################################################
+
+statePropertyElement = Element
+statePropertyStateName = State
+statePropertyStartTime = Start Time
+statePropertyEndTime = End Time
+statePropertyDuration = Duration
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc. and others
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *******************************************************************************/
+
+@org.eclipse.jdt.annotation.NonNullByDefault
+package org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph;
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.toolbar;
+
+import java.util.Collection;
+import java.util.stream.Collectors;
+
+import org.lttng.scope.tmf2.views.core.timegraph.model.provider.ITimeGraphModelProvider;
+import org.lttng.scope.tmf2.views.core.timegraph.model.provider.arrows.ITimeGraphModelArrowProvider;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.arrows.TimeGraphArrowSeries;
+import org.lttng.scope.tmf2.views.ui.jfx.Arrow;
+import org.lttng.scope.tmf2.views.ui.jfx.JfxColorFactory;
+import org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.TimeGraphWidget;
+
+import javafx.scene.control.CheckMenuItem;
+import javafx.scene.control.MenuButton;
+import javafx.scene.paint.Color;
+
+/**
+ * Menu-button for listing the available arrow series.
+ *
+ * The available arrow series come from the time graph model. More than one mode
+ * (or none) can be active at the same time, so we are using CheckMenuItems for
+ * the menu items.
+ *
+ * @author Alexandre Montplaisir
+ */
+class ArrowSeriesMenuButton extends MenuButton {
+
+ private static final double ARROW_GRAPHIC_LENGTH = 10;
+
+ public ArrowSeriesMenuButton(TimeGraphWidget widget) {
+ ITimeGraphModelProvider modelProvider = widget.getControl().getModelRenderProvider();
+ Collection<ITimeGraphModelArrowProvider> arrowProviders = modelProvider.getArrowProviders();
+
+ Collection<CheckMenuItem> arrowSeriesItems = arrowProviders.stream()
+ .map(arrowProvider -> {
+ TimeGraphArrowSeries series = arrowProvider.getArrowSeries();
+ String name = series.getSeriesName();
+ Arrow graphic = getArrowGraphicForSeries(series);
+ CheckMenuItem cmi = new CheckMenuItem(name, graphic);
+ cmi.selectedProperty().bindBidirectional(arrowProvider.enabledProperty());
+ return cmi;
+ })
+ .collect(Collectors.toList());
+
+ setText(Messages.arrowSeriesMenuButtonName);
+ getItems().addAll(arrowSeriesItems);
+
+ if (arrowSeriesItems.isEmpty()) {
+ setDisable(true);
+ }
+ }
+
+ private static Arrow getArrowGraphicForSeries(TimeGraphArrowSeries series) {
+ Color color = JfxColorFactory.getColorFromDef(series.getColor());
+ Arrow arrow = new Arrow(0, 0, ARROW_GRAPHIC_LENGTH, 0);
+ arrow.setStroke(color);
+ return arrow;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.toolbar;
+
+import java.util.Collection;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+import org.lttng.scope.tmf2.views.core.timegraph.model.provider.ITimeGraphModelProvider;
+import org.lttng.scope.tmf2.views.core.timegraph.model.provider.ITimeGraphModelProvider.FilterMode;
+import org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.TimeGraphWidget;
+
+import javafx.scene.control.CheckMenuItem;
+import javafx.scene.control.MenuButton;
+
+/**
+ * Menu-button for listing the filter modes.
+ *
+ * The available filter modes come from the time graph model. More than one mode
+ * (or none) can be active at the same time, so we are using CheckMenuItems for
+ * the menu items.
+ *
+ * @author Alexandre Montplaisir
+ */
+class FilterModeMenuButton extends MenuButton {
+
+ public FilterModeMenuButton(TimeGraphWidget viewer) {
+ ITimeGraphModelProvider provider = viewer.getControl().getModelRenderProvider();
+
+ Collection<CheckMenuItem> filterModeItems = IntStream.range(0, provider.getFilterModes().size())
+ .mapToObj(index -> {
+ FilterMode fm = provider.getFilterModes().get(index);
+ CheckMenuItem cmi = new CheckMenuItem(fm.getName());
+ cmi.setOnAction(e -> {
+ if (cmi.isSelected()) {
+ /* Mode was enabled */
+ provider.enableFilterMode(index);
+ } else {
+ /* Mode was disabled */
+ provider.disableFilterMode(index);
+ }
+ viewer.getControl().repaintCurrentArea();
+ });
+ return cmi;
+ })
+ .collect(Collectors.toList());
+
+ setText(Messages.sfFilterModeMenuButtonName);
+ getItems().addAll(filterModeItems);
+
+ if (filterModeItems.isEmpty()) {
+ setDisable(true);
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc.
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.toolbar;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * Message bundle for the package
+ *
+ * @noreference Messages class
+ */
+@NonNullByDefault({})
+@SuppressWarnings("javadoc")
+public class Messages extends NLS {
+
+ private static final String BUNDLE_NAME = Messages.class.getPackage().getName() + ".messages"; //$NON-NLS-1$
+
+ public static String arrowSeriesMenuButtonName;
+
+ public static String sfFilterModeMenuButtonName;
+ public static String sfSortingModeMenuButtonName;
+
+ public static String sfZoomInActionDescription;
+ public static String sfZoomOutActionDescription;
+ public static String sfZoomToFullRangeActionDescription;
+ public static String sfZoomToSelectionActionDescription;
+
+ static {
+ NLS.initializeMessages(BUNDLE_NAME, Messages.class);
+ }
+
+ private Messages() {
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.toolbar;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+import org.lttng.scope.tmf2.views.core.timegraph.model.provider.ITimeGraphModelProvider;
+import org.lttng.scope.tmf2.views.core.timegraph.model.provider.ITimeGraphModelProvider.SortingMode;
+import org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.TimeGraphWidget;
+
+import javafx.scene.control.MenuButton;
+import javafx.scene.control.RadioMenuItem;
+import javafx.scene.control.ToggleGroup;
+
+/**
+ * Menu button for selecting the sorting mode of tree entries.
+ *
+ * The available modes are defined by the time graph model. Only one mode can be
+ * active at a time, so we are using RadioMenuItems for the menu items.
+ *
+ * @author Alexandre Montplaisir
+ */
+class SortingModeMenuButton extends MenuButton {
+
+ public SortingModeMenuButton(TimeGraphWidget viewer) {
+ ITimeGraphModelProvider provider = viewer.getControl().getModelRenderProvider();
+
+ ToggleGroup tg = new ToggleGroup();
+ List<RadioMenuItem> sortingModeItems = IntStream.range(0, provider.getSortingModes().size())
+ .mapToObj(index -> {
+ SortingMode sm = provider.getSortingModes().get(index);
+ RadioMenuItem rmi = new RadioMenuItem(sm.getName());
+ rmi.setToggleGroup(tg);
+ rmi.setOnAction(e -> {
+ provider.setCurrentSortingMode(index);
+ viewer.getControl().repaintCurrentArea();
+ });
+ return rmi;
+ })
+ .collect(Collectors.toList());
+
+ if (!sortingModeItems.isEmpty()) {
+ /*
+ * Initialize the first mode to be selected, which is what the model
+ * does. This should not trigger the event handler.
+ */
+ sortingModeItems.get(0).setSelected(true);
+ }
+
+ setText(Messages.sfSortingModeMenuButtonName);
+ getItems().addAll(sortingModeItems);
+
+ /*
+ * There is at minimum the "default" sorting mode. Don't show the list
+ * if there is only that one.
+ */
+ if (sortingModeItems.size() <= 1) {
+ setDisable(true);
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.toolbar;
+
+import org.lttng.scope.tmf2.views.ui.jfx.JfxImageFactory;
+import org.lttng.scope.tmf2.views.ui.jfx.JfxUtils;
+import org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.StateRectangle;
+import org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.TimeGraphWidget;
+import org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.toolbar.debugopts.DebugOptionsButton;
+import org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.toolbar.drawnevents.EventSeriesMenuButton;
+import org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.toolbar.modelconfig.ModelConfigButton;
+import org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.toolbar.nav.NavigationButtons;
+
+import javafx.scene.control.Alert;
+import javafx.scene.control.Alert.AlertType;
+import javafx.scene.control.Button;
+import javafx.scene.control.Label;
+import javafx.scene.control.Separator;
+import javafx.scene.control.TextField;
+import javafx.scene.control.ToolBar;
+import javafx.scene.control.Tooltip;
+import javafx.scene.image.Image;
+import javafx.scene.image.ImageView;
+import javafx.scene.layout.HBox;
+
+/**
+ * Toolbar for the time graph viewer.
+ *
+ * @author Alexandre Montplaisir
+ */
+public class ViewerToolBar extends ToolBar {
+
+ private static final String HELP_ICON_PATH = "/icons/toolbar/help.gif"; //$NON-NLS-1$
+
+ /**
+ * Constructor
+ *
+ * @param viewer
+ * The time graph viewer to which this toolbar belongs.
+ */
+ public ViewerToolBar(TimeGraphWidget viewer) {
+ super();
+
+ NavigationButtons navButtons = new NavigationButtons(viewer);
+
+ getItems().addAll(
+ new Label(viewer.getName()),
+ new Separator(),
+
+ new ZoomInButton(viewer),
+ new ZoomOutButton(viewer),
+ new ZoomToSelectionButton(viewer),
+ new ZoomToFullRangeButton(viewer),
+ new Separator(),
+
+ new HBox(
+ navButtons.getBackButton(),
+ navButtons.getForwardButton(),
+ navButtons.getMenuButton()
+ ),
+ getStateInfoButton(viewer),
+ new Separator(),
+
+ new ModelConfigButton(viewer),
+ new ArrowSeriesMenuButton(viewer),
+ new EventSeriesMenuButton(viewer),
+ new SortingModeMenuButton(viewer),
+ new FilterModeMenuButton(viewer),
+ new Separator(),
+
+ new DebugOptionsButton(viewer));
+ }
+
+ // FIXME Temporary, should be moved to tooltip
+ private Button getStateInfoButton(TimeGraphWidget viewer) {
+ Button button = new Button();
+ Image helpIcon = JfxImageFactory.instance().getImageFromResource(HELP_ICON_PATH);
+ button.setGraphic(new ImageView(helpIcon));
+ button.setTooltip(new Tooltip("Get State Info")); //$NON-NLS-1$
+ button.setOnAction(e -> {
+ StateRectangle state = viewer.getSelectedState();
+ if (state == null) {
+ return;
+ }
+ Alert alert = new Alert(AlertType.INFORMATION);
+ /* Use a read-only TextField so the text can be copy-pasted */
+ TextField content = new TextField(state.toString());
+ content.setEditable(false);
+ content.setPrefWidth(1000.0);
+ alert.getDialogPane().setContent(content);
+ alert.setResizable(true);
+ alert.show();
+
+ JfxUtils.centerDialogOnScreen(alert, this);
+ });
+ return button;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.toolbar;
+
+import org.lttng.scope.tmf2.views.ui.jfx.JfxImageFactory;
+import org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.TimeGraphWidget;
+
+import javafx.scene.control.Button;
+import javafx.scene.control.Tooltip;
+import javafx.scene.image.Image;
+import javafx.scene.image.ImageView;
+
+/**
+ * Button for zooming in. It should do the same action as one ctrl+mouse-scroll.
+ *
+ * @author Alexandre Montplaisir
+ */
+class ZoomInButton extends Button {
+
+ private static final String ZOOM_IN_ICON_PATH = "/icons/toolbar/zoom_in.gif"; //$NON-NLS-1$
+
+ public ZoomInButton(TimeGraphWidget viewer) {
+ Image icon = JfxImageFactory.instance().getImageFromResource(ZOOM_IN_ICON_PATH);
+ setGraphic(new ImageView(icon));
+ setTooltip(new Tooltip(Messages.sfZoomInActionDescription));
+ setOnAction(e -> {
+ viewer.getZoomActions().zoom(true, false, null);
+ });
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.toolbar;
+
+import org.lttng.scope.tmf2.views.ui.jfx.JfxImageFactory;
+import org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.TimeGraphWidget;
+
+import javafx.scene.control.Button;
+import javafx.scene.control.Tooltip;
+import javafx.scene.image.Image;
+import javafx.scene.image.ImageView;
+
+/**
+ * Button for zooming out. It should do the same action as one
+ * ctrl+mouse-scroll.
+ *
+ * @author Alexandre Montplaisir
+ */
+class ZoomOutButton extends Button {
+
+ private static final String ZOOM_OUT_ICON_PATH = "/icons/toolbar/zoom_out.gif"; //$NON-NLS-1$
+
+ public ZoomOutButton(TimeGraphWidget viewer) {
+ Image icon = JfxImageFactory.instance().getImageFromResource(ZOOM_OUT_ICON_PATH);
+ setGraphic(new ImageView(icon));
+ setTooltip(new Tooltip(Messages.sfZoomOutActionDescription));
+ setOnAction(e -> {
+ viewer.getZoomActions().zoom(false, false, null);
+ });
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.toolbar;
+
+import org.lttng.scope.tmf2.views.core.TimeRange;
+import org.lttng.scope.tmf2.views.ui.jfx.JfxImageFactory;
+import org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.TimeGraphWidget;
+
+import javafx.scene.control.Button;
+import javafx.scene.control.Tooltip;
+import javafx.scene.image.Image;
+import javafx.scene.image.ImageView;
+
+/**
+ * Button to zoom (out) to the full trace's range.
+ *
+ * @author Alexandre Montplaisir
+ */
+class ZoomToFullRangeButton extends Button {
+
+ private static final String ZOOM_TO_FULL_RANGE_ICON_PATH = "/icons/toolbar/zoom_full.gif"; //$NON-NLS-1$
+
+ public ZoomToFullRangeButton(TimeGraphWidget viewer) {
+ Image icon = JfxImageFactory.instance().getImageFromResource(ZOOM_TO_FULL_RANGE_ICON_PATH);
+ setGraphic(new ImageView(icon));
+ setTooltip(new Tooltip(Messages.sfZoomToFullRangeActionDescription));
+ setOnAction(e -> {
+ TimeRange fullRange = viewer.getControl().getViewContext().getCurrentTraceFullRange();
+ viewer.getControl().updateVisibleTimeRange(fullRange, true);
+ });
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.toolbar;
+
+import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimeRange;
+import org.eclipse.tracecompass.tmf.core.trace.TmfTraceManager;
+import org.lttng.scope.tmf2.views.core.TimeRange;
+import org.lttng.scope.tmf2.views.ui.jfx.JfxImageFactory;
+import org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.TimeGraphWidget;
+
+import javafx.scene.control.Button;
+import javafx.scene.control.Tooltip;
+import javafx.scene.image.Image;
+import javafx.scene.image.ImageView;
+
+/**
+ * Button to zoom into the current selection, if there is one.
+ *
+ * @author Alexandre Montplaisir
+ */
+class ZoomToSelectionButton extends Button {
+
+ // TODO Get an icon!
+ private static final String ZOOM_TO_SELECTION_ICON_PATH = "/icons/toolbar/zoom_in.gif"; //$NON-NLS-1$
+
+ public ZoomToSelectionButton(TimeGraphWidget viewer) {
+ Image icon = JfxImageFactory.instance().getImageFromResource(ZOOM_TO_SELECTION_ICON_PATH);
+ setGraphic(new ImageView(icon));
+ setTooltip(new Tooltip(Messages.sfZoomToSelectionActionDescription));
+ setOnAction(e -> {
+ TmfTimeRange range = TmfTraceManager.getInstance().getCurrentTraceContext().getSelectionRange();
+ TimeRange timeRange = TimeRange.fromTmfTimeRange(range);
+ /*
+ * Only actually zoom if the selection is a time range, not a single
+ * timestamp.
+ */
+ if (timeRange.getDuration() > 0) {
+ viewer.getControl().updateVisibleTimeRange(timeRange, true);
+ }
+ });
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.toolbar.debugopts;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.lttng.scope.tmf2.views.ui.jfx.JfxImageFactory;
+import org.lttng.scope.tmf2.views.ui.jfx.JfxUtils;
+import org.lttng.scope.tmf2.views.ui.timeline.DebugOptions;
+import org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.TimeGraphWidget;
+
+import javafx.beans.property.IntegerProperty;
+import javafx.beans.property.SimpleIntegerProperty;
+import javafx.scene.control.Button;
+import javafx.scene.control.Dialog;
+import javafx.scene.control.Tooltip;
+import javafx.scene.image.Image;
+import javafx.scene.image.ImageView;
+
+/**
+ * Button to open the debug options dialog.
+ *
+ * @author Alexandre Montplaisir
+ */
+public class DebugOptionsButton extends Button {
+
+ private static final String CONFIG_ICON_PATH = "/icons/toolbar/config.gif"; //$NON-NLS-1$
+
+ /*
+ * Since the button is more persistent than the dialog, track state like the
+ * last-selected tab in here.
+ */
+ private final IntegerProperty fLastSelectedDialogTab = new SimpleIntegerProperty(0);
+
+ private final DebugOptions fOpts;
+
+ /**
+ * Constructor
+ *
+ * @param widget
+ * The time graph widget to which this toolbar button is
+ * associated.
+ */
+ public DebugOptionsButton(TimeGraphWidget widget) {
+ Image icon = JfxImageFactory.instance().getImageFromResource(CONFIG_ICON_PATH);
+ setGraphic(new ImageView(icon));
+ setTooltip(new Tooltip(Messages.debugOptionsDialogName));
+
+ fOpts = widget.getDebugOptions();
+
+ setOnAction(e -> {
+ Dialog<@Nullable Void> dialog = new DebugOptionsDialog(this);
+ dialog.show();
+ JfxUtils.centerDialogOnScreen(dialog, DebugOptionsButton.this);
+ });
+ }
+
+ DebugOptions getDebugOptions() {
+ return fOpts;
+ }
+
+ IntegerProperty lastSelectedDialogProperty() {
+ return fLastSelectedDialogTab;
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.toolbar.debugopts;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.stream.Stream;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.lttng.scope.tmf2.views.core.config.ConfigOption;
+import org.lttng.scope.tmf2.views.ui.timeline.DebugOptions;
+
+import com.google.common.primitives.Doubles;
+import com.google.common.primitives.Ints;
+
+import javafx.event.ActionEvent;
+import javafx.geometry.Insets;
+import javafx.geometry.Pos;
+import javafx.scene.Node;
+import javafx.scene.control.ButtonBar.ButtonData;
+import javafx.scene.control.ButtonType;
+import javafx.scene.control.CheckBox;
+import javafx.scene.control.ColorPicker;
+import javafx.scene.control.Dialog;
+import javafx.scene.control.Label;
+import javafx.scene.control.Tab;
+import javafx.scene.control.TabPane;
+import javafx.scene.control.TextField;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.VBox;
+import javafx.scene.paint.Color;
+
+/**
+ * Dialog to configure the debug options at runtime.
+ *
+ * @author Alexandre Montplaisir
+ */
+class DebugOptionsDialog extends Dialog<@Nullable Void> {
+
+ private static final Insets PADDING = new Insets(20.0);
+ private static final double SPACING = 10.0;
+
+ private final DebugOptions fOpts;
+ private final TabPane fTabPane;
+
+ public DebugOptionsDialog(DebugOptionsButton button) {
+ fOpts = button.getDebugOptions();
+
+ setTitle(Messages.debugOptionsDialogTitle);
+ setHeaderText(Messages.debugOptionsDialogName);
+
+ ButtonType resetToDefaultButtonType = new ButtonType(Messages.resetDefaultsButtonLabel, ButtonData.LEFT);
+ getDialogPane().getButtonTypes().addAll(resetToDefaultButtonType, ButtonType.CANCEL, ButtonType.OK);
+
+ fTabPane = new TabPane(getGeneralTab(),
+ getLoadingOverlayTab(),
+ getZoomTab(),
+ getStateIntervalsTab(),
+ getTooltipTab());
+ getDialogPane().setContent(fTabPane);
+
+ /*
+ * Restore the last-selected tab (that state is saved in the button),
+ * and re-bind on the new dialog so that the property continues getting
+ * updated.
+ */
+ fTabPane.getSelectionModel().select(button.lastSelectedDialogProperty().get());
+ button.lastSelectedDialogProperty().bind(fTabPane.getSelectionModel().selectedIndexProperty());
+
+ /* What to do when the dialog is closed */
+ setResultConverter(dialogButton -> {
+ fTabPane.getSelectionModel().getSelectedIndex();
+
+ if (dialogButton != ButtonType.OK) {
+ return null;
+ }
+ /*
+ * Set the debug options according to the current contents of the
+ * dialog.
+ */
+ getAllPropertySetters().forEach(PropertySetter::save);
+
+ return null;
+ });
+
+ /* Define how to "Reset Defaults" button works */
+ getDialogPane().lookupButton(resetToDefaultButtonType).addEventFilter(ActionEvent.ACTION, e -> {
+ /*
+ * This button should not close the dialog. Consuming the event here
+ * will prevent the dialog from closing.
+ */
+ e.consume();
+
+ getAllPropertySetters().forEach(ps -> {
+ ConfigOption<?> option = ps.getOption();
+ option.resetToDefault();
+ ps.load();
+ });
+
+ });
+
+ }
+
+ private Stream<PropertySetter> getAllPropertySetters() {
+ return fTabPane.getTabs().stream()
+ .flatMap(tab -> ((VBox) tab.getContent()).getChildren().stream())
+ .map(e -> requireNonNull((PropertySetter) e));
+ }
+
+ // ------------------------------------------------------------------------
+ // Tab classes
+ // ------------------------------------------------------------------------
+
+ private static class DebugOptionsDialogTab extends Tab {
+
+ public DebugOptionsDialogTab(@Nullable String name, Node... contents) {
+ VBox page = new VBox(contents);
+ page.setPadding(PADDING);
+ page.setSpacing(SPACING);
+
+ setClosable(false);
+ setText(name);
+ setContent(page);
+ }
+
+ }
+
+ private Tab getGeneralTab() {
+ return new DebugOptionsDialogTab(Messages.tabNameGeneral,
+ new CheckBoxControl(Messages.controlPaintingEnabled, fOpts.isPaintingEnabled),
+ new IntegerTextField(Messages.controlEntryPadding, fOpts.entryPadding),
+ new DoubleTextField(Messages.controlRenderRangePadding, fOpts.renderRangePadding),
+ new IntegerTextField(Messages.controlUIUpdateDelay, fOpts.uiUpdateDelay),
+ new CheckBoxControl(Messages.controlHScrollEnabled, fOpts.isScrollingListenersEnabled));
+ }
+
+ private Tab getLoadingOverlayTab() {
+ return new DebugOptionsDialogTab(Messages.tabNameLoadingOverlay,
+ new CheckBoxControl(Messages.controlLoadingOverlayEnabled, fOpts.isLoadingOverlayEnabled),
+ new ColorControl(Messages.controlLoadingOverlayColor, fOpts.loadingOverlayColor),
+ new DoubleTextField(Messages.controlLoadingOverlayFullOpacity, fOpts.loadingOverlayFullOpacity),
+// new DoubleTextField(Messages.controlLoadingOverlayTransparentOpacity, fOpts.loadingOverlayTransparentOpacity),
+ new DoubleTextField(Messages.controlLoadingOverlayFadeIn, fOpts.loadingOverlayFadeInDuration),
+ new DoubleTextField(Messages.controlLoadingOverlayFadeOut, fOpts.loadingOverlayFadeOutDuration));
+ }
+
+ private Tab getZoomTab() {
+ return new DebugOptionsDialogTab(Messages.tabNameZoom,
+ new IntegerTextField(Messages.controlZoomAnimationDuration + " (unused)", fOpts.zoomAnimationDuration), //$NON-NLS-1$
+ new DoubleTextField(Messages.controlZoomStep, fOpts.zoomStep),
+ new CheckBoxControl(Messages.controlZoomPivotOnSelection, fOpts.zoomPivotOnSelection),
+ new CheckBoxControl(Messages.controlZoomPivotOnMousePosition, fOpts.zoomPivotOnMousePosition));
+ }
+
+ private Tab getStateIntervalsTab() {
+ return new DebugOptionsDialogTab(Messages.tabNameIntervals,
+ new DoubleTextField(Messages.controlIntervalOpacity, fOpts.stateIntervalOpacity)
+ // multi-state Paint ?
+ // state label Font ?
+ );
+ }
+
+ private Tab getTooltipTab() {
+ return new DebugOptionsDialogTab(Messages.tabNameTooltips,
+ // Tooltip Font picker
+ new ColorControl(Messages.controlTooltipFontColor, fOpts.toolTipFontFill));
+ }
+
+ // ------------------------------------------------------------------------
+ // Property-setting controls
+ // ------------------------------------------------------------------------
+
+ private static interface PropertySetter {
+ ConfigOption<?> getOption();
+ void load();
+ void save();
+ }
+
+ private static class CheckBoxControl extends CheckBox implements PropertySetter {
+
+ private final ConfigOption<Boolean> fOption;
+
+ public CheckBoxControl(@Nullable String labelText, ConfigOption<Boolean> option) {
+ fOption = option;
+ setText(labelText);
+ load();
+ }
+
+ @Override
+ public ConfigOption<?> getOption() {
+ return fOption;
+ }
+
+ @Override
+ public void load() {
+ setSelected(fOption.get());
+ }
+
+ @Override
+ public void save() {
+ fOption.set(isSelected());
+ }
+ }
+
+ private static class ColorControl extends HBox implements PropertySetter {
+
+ private final ConfigOption<Color> fOption;
+ private final ColorPicker fColorPicker;
+
+ public ColorControl(@Nullable String labelText, ConfigOption<Color> option) {
+ fOption = option;
+
+ Label label = new Label(labelText + ":"); //$NON-NLS-1$
+ fColorPicker = new ColorPicker(option.get());
+
+ getChildren().addAll(label, fColorPicker);
+ setAlignment(Pos.CENTER_LEFT);
+ setSpacing(SPACING);
+ }
+
+ @Override
+ public ConfigOption<?> getOption() {
+ return fOption;
+ }
+
+ @Override
+ public void load() {
+ fColorPicker.setValue(fOption.get());
+ }
+
+ @Override
+ public void save() {
+ Color color = fColorPicker.getValue();
+ if (color != null) {
+ fOption.set(color);
+ }
+ }
+ }
+
+ private static abstract class TextFieldControl<T extends Number> extends HBox implements PropertySetter {
+
+ private final ConfigOption<T> fOption;
+ private final TextField fTextField = new TextField();
+
+ protected TextFieldControl(@Nullable String labelText, ConfigOption<T> option) {
+ fOption = option;
+
+ Label label = new Label(labelText + ":"); //$NON-NLS-1$
+ load();
+
+ getChildren().addAll(label, fTextField);
+ setAlignment(Pos.CENTER_LEFT);
+ setSpacing(SPACING);
+ }
+
+ @Override
+ public ConfigOption<T> getOption() {
+ return fOption;
+ }
+
+ @Override
+ public void load() {
+ fTextField.setText(fOption.get().toString());
+ }
+
+ @Override
+ public abstract void save();
+
+ protected TextField getTextField() {
+ return fTextField;
+ }
+ }
+
+ private static class IntegerTextField extends TextFieldControl<Integer> {
+
+ public IntegerTextField(@Nullable String labelText, ConfigOption<Integer> option) {
+ super(labelText, option);
+ }
+
+ @Override
+ public void save() {
+ String text = getTextField().getText();
+ Integer value = Ints.tryParse(text);
+ if (value == null) {
+ getOption().resetToDefault();
+ load();
+ } else {
+ getOption().set(value);
+ }
+ }
+ }
+
+ private static class DoubleTextField extends TextFieldControl<Double> {
+
+ public DoubleTextField(@Nullable String labelText, ConfigOption<Double> option) {
+ super(labelText, option);
+ }
+
+ @Override
+ public void save() {
+ String text = getTextField().getText();
+ Double value = Doubles.tryParse(text);
+ if (value == null) {
+ getOption().resetToDefault();
+ load();
+ } else {
+ getOption().set(value);
+ }
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc.
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.toolbar.debugopts;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * Message bundle for the package
+ *
+ * @noreference Messages class
+ */
+@NonNullByDefault({})
+@SuppressWarnings("javadoc")
+public class Messages extends NLS {
+
+ private static final String BUNDLE_NAME = Messages.class.getPackage().getName() + ".messages"; //$NON-NLS-1$
+
+ public static String debugOptionsDialogTitle;
+ public static String debugOptionsDialogName;
+
+ public static String resetDefaultsButtonLabel;
+
+ public static String tabNameGeneral;
+ public static String controlPaintingEnabled;
+ public static String controlEntryPadding;
+ public static String controlRenderRangePadding;
+ public static String controlUIUpdateDelay;
+ public static String controlHScrollEnabled;
+
+ public static String tabNameLoadingOverlay;
+ public static String controlLoadingOverlayEnabled;
+ public static String controlLoadingOverlayColor;
+ public static String controlLoadingOverlayFullOpacity;
+ public static String controlLoadingOverlayTransparentOpacity;
+ public static String controlLoadingOverlayFadeIn;
+ public static String controlLoadingOverlayFadeOut;
+
+ public static String tabNameZoom;
+ public static String controlZoomAnimationDuration;
+ public static String controlZoomStep;
+ public static String controlZoomPivotOnSelection;
+ public static String controlZoomPivotOnMousePosition;
+
+ public static String tabNameIntervals;
+ public static String controlIntervalOpacity;
+
+ public static String tabNameTooltips;
+ public static String controlTooltipFontColor;
+
+ static {
+ NLS.initializeMessages(BUNDLE_NAME, Messages.class);
+ }
+
+ private Messages() {
+ }
+}
--- /dev/null
+###############################################################################
+# Copyright (c) 2017 EfficiOS Inc.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Eclipse Public License v1.0
+# which accompanies this distribution, and is available at
+# http://www.eclipse.org/legal/epl-v10.html
+###############################################################################
+
+debugOptionsDialogTitle = Bikeshedding
+debugOptionsDialogName = Advanced View Configuration
+
+resetDefaultsButtonLabel = Reset Defaults
+
+tabNameGeneral = General
+controlPaintingEnabled = Painting enabled
+controlEntryPadding = Entry Padding
+controlRenderRangePadding = Render time range padding
+controlUIUpdateDelay = UI Update Delay (ms)
+controlHScrollEnabled = HScrolling listener enabled
+
+tabNameLoadingOverlay = Loading Overlay
+controlLoadingOverlayEnabled = Loading overlay enabled
+controlLoadingOverlayColor = Overlay color
+controlLoadingOverlayFullOpacity = Full Opacity
+controlLoadingOverlayTransparentOpacity = Transparent Opacity
+controlLoadingOverlayFadeIn = Fade-in duration (ms)
+controlLoadingOverlayFadeOut = Fade-out duration (ms)
+
+tabNameZoom = Zoom
+controlZoomAnimationDuration = Zoom animation duration (ms)
+controlZoomStep = Zoom step
+controlZoomPivotOnSelection = Zoom pivot on selection
+controlZoomPivotOnMousePosition = Zoom pivot on mouse position
+
+tabNameIntervals = Intervals
+controlIntervalOpacity = State interval opacity
+
+tabNameTooltips = Tooltips
+controlTooltipFontColor = Font color
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+@org.eclipse.jdt.annotation.NonNullByDefault
+package org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.toolbar.debugopts;
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.toolbar.drawnevents;
+
+import java.util.function.Predicate;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.tmf.core.event.ITmfEvent;
+import org.lttng.scope.tmf2.views.core.config.ConfigOption;
+import org.lttng.scope.tmf2.views.core.timegraph.model.provider.ITimeGraphModelProvider;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.ColorDefinition;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.drawnevents.TimeGraphDrawnEventSeries;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.drawnevents.TimeGraphDrawnEventSeries.SymbolStyle;
+import org.lttng.scope.tmf2.views.ui.jfx.CountingGridPane;
+import org.lttng.scope.tmf2.views.ui.jfx.JfxColorFactory;
+import org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.layer.TimeGraphDrawnEventLayer;
+
+import javafx.beans.property.ReadOnlyProperty;
+import javafx.geometry.Insets;
+import javafx.geometry.Pos;
+import javafx.scene.Node;
+import javafx.scene.control.Button;
+import javafx.scene.control.ButtonType;
+import javafx.scene.control.ColorPicker;
+import javafx.scene.control.ComboBox;
+import javafx.scene.control.ContentDisplay;
+import javafx.scene.control.Dialog;
+import javafx.scene.control.Label;
+import javafx.scene.control.ListCell;
+import javafx.scene.control.ListView;
+import javafx.scene.control.TextField;
+import javafx.scene.layout.VBox;
+import javafx.scene.paint.Color;
+import javafx.scene.shape.Shape;
+import javafx.scene.text.Font;
+import javafx.scene.text.FontPosture;
+import javafx.scene.text.FontWeight;
+import javafx.util.Callback;
+
+class CreateEventSeriesDialog extends Dialog<@Nullable PredicateDrawnEventProvider> {
+
+ private static final Color DEFAULT_SYMBOL_COLOR = Color.ORANGE;
+ private static final double PADDING = 10;
+
+ private static final Font HEADER_FONT = Font.font(null, FontWeight.BOLD, FontPosture.REGULAR, -1);
+ private static final Insets HEADER_PADDING = new Insets(10, 0, 10, 0);
+
+ private final TextField fEventNameField;
+
+ private final ColorPicker fSymbolColorPicker;
+ private final ShapePicker fSymbolShapePicker;
+
+ public CreateEventSeriesDialog(ITimeGraphModelProvider modelProvider) {
+ setTitle(Messages.createEventSeriesDialogTitle);
+
+ /* Dialog buttons, standard "OK" and "Cancel" */
+ getDialogPane().getButtonTypes().addAll(ButtonType.CANCEL, ButtonType.OK);
+
+ /* Dialog contents */
+ Label filterHeader = new Label(Messages.createEventSeriesDialogSectionFilterDef);
+ filterHeader.setFont(HEADER_FONT);
+ filterHeader.setPadding(HEADER_PADDING);
+
+ fEventNameField = new TextField();
+ CountingGridPane filterGrid = new CountingGridPane();
+ filterGrid.setHgap(PADDING);
+ filterGrid.setVgap(PADDING);
+ filterGrid.appendRow(new Label(Messages.createEventSeriesDialogFieldEventName), fEventNameField);
+
+ Label symbolHeader = new Label(Messages.createEventSeriesDialogSectionSymbolDef);
+ symbolHeader.setFont(HEADER_FONT);
+ symbolHeader.setPadding(HEADER_PADDING);
+
+ fSymbolColorPicker = new ColorPicker(DEFAULT_SYMBOL_COLOR);
+ fSymbolShapePicker = new ShapePicker(fSymbolColorPicker.valueProperty());
+ CountingGridPane symbolGrid = new CountingGridPane();
+ symbolGrid.setHgap(PADDING);
+ symbolGrid.setVgap(PADDING);
+ symbolGrid.appendRow(new Label(Messages.createEventSeriesDialogFieldColor), fSymbolColorPicker);
+ symbolGrid.appendRow(new Label(Messages.createEventSeriesDialogFieldShape), fSymbolShapePicker);
+
+ VBox vbox = new VBox(filterHeader, filterGrid, symbolHeader, symbolGrid);
+ vbox.setAlignment(Pos.CENTER);
+ getDialogPane().setContent(vbox);
+
+ /*
+ * Disable the OK button until the input is valid
+ *
+ * TODO ControlsFX Validation framework might be useful when more
+ * fields/options are added.
+ */
+ final Button okButton = (Button) getDialogPane().lookupButton(ButtonType.OK);
+ okButton.setDisable(true);
+ fEventNameField.textProperty().addListener((observable, oldValue, newValue) -> {
+ okButton.setDisable(newValue.trim().length() <= 0);
+ });
+
+ /* What to do when the dialog is closed */
+ setResultConverter(dialogButton -> {
+ if (dialogButton != ButtonType.OK) {
+ return null;
+ }
+
+ String eventName = fEventNameField.getText();
+ if (eventName == null || eventName.isEmpty()) {
+ return null;
+ }
+
+ TimeGraphDrawnEventSeries series = generateEventSeries();
+ Predicate<ITmfEvent> predicate = event -> event.getName().equals(eventName);
+ return new PredicateDrawnEventProvider(series, modelProvider, predicate);
+ });
+
+ }
+
+ /**
+ * Generate an event series from the current value of the controls
+ *
+ * @return The corresponding event series
+ */
+ private TimeGraphDrawnEventSeries generateEventSeries() {
+ String seriesName = fEventNameField.getText();
+ ColorDefinition colorDef = JfxColorFactory.colorToColorDef(fSymbolColorPicker.getValue());
+ SymbolStyle style = fSymbolShapePicker.getSelectionModel().getSelectedItem();
+
+ return new TimeGraphDrawnEventSeries(
+ seriesName == null ? "" : seriesName, //$NON-NLS-1$
+ new ConfigOption<>(colorDef),
+ new ConfigOption<>(style));
+ }
+
+ private static class ShapePicker extends ComboBox<SymbolStyle> {
+
+ public ShapePicker(ReadOnlyProperty<Color> colorSource) {
+ getItems().addAll(SymbolStyle.values());
+
+ Callback<@Nullable ListView<SymbolStyle>, ListCell<SymbolStyle>> cellFactory =
+ p -> new ListCell<SymbolStyle>() {
+ {
+ setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
+ }
+
+ @Override
+ protected void updateItem(SymbolStyle item, boolean empty) {
+ super.updateItem(item, empty);
+ if (empty) {
+ setGraphic(null);
+ } else {
+ Node graphic = getGraphicFromSymbol(item, colorSource);
+ setGraphic(graphic);
+ }
+ }
+ };
+
+ setButtonCell(cellFactory.call(null));
+ setCellFactory(cellFactory);
+
+ /* Select the first symbol by default */
+ getSelectionModel().select(0);
+ }
+
+ private static Node getGraphicFromSymbol(SymbolStyle symbol, ReadOnlyProperty<Color> colorSource) {
+ Shape graphic = TimeGraphDrawnEventLayer.getShapeFromSymbol(symbol);
+ graphic.fillProperty().bind(colorSource);
+ return graphic;
+ }
+
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.toolbar.drawnevents;
+
+import java.util.Optional;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.lttng.scope.tmf2.views.core.timegraph.model.provider.ITimeGraphModelProvider;
+import org.lttng.scope.tmf2.views.core.timegraph.model.provider.drawnevents.ITimeGraphDrawnEventProvider;
+import org.lttng.scope.tmf2.views.core.timegraph.model.provider.drawnevents.TimeGraphDrawnEventProviderManager;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.drawnevents.TimeGraphDrawnEventSeries;
+import org.lttng.scope.tmf2.views.ui.jfx.JfxColorFactory;
+import org.lttng.scope.tmf2.views.ui.jfx.JfxUtils;
+import org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.TimeGraphWidget;
+import org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.layer.TimeGraphDrawnEventLayer;
+
+import com.google.common.collect.Iterables;
+
+import javafx.application.Platform;
+import javafx.collections.ListChangeListener;
+import javafx.collections.SetChangeListener;
+import javafx.scene.control.CheckMenuItem;
+import javafx.scene.control.MenuButton;
+import javafx.scene.control.MenuItem;
+import javafx.scene.control.SeparatorMenuItem;
+import javafx.scene.paint.Color;
+import javafx.scene.shape.Shape;
+
+/**
+ * Menu button showing listing the existing drawn-event providers, with menu
+ * items to create/clear such providers.
+ *
+ * TODO This button and its related actions are independent from a time graph
+ * widget. They could be moved elsewhere in the UI at some point.
+ *
+ * @author Alexandre Montplaisir
+ */
+public class EventSeriesMenuButton extends MenuButton {
+
+ private static final TimeGraphDrawnEventProviderManager PROVIDER_MANAGER =
+ TimeGraphDrawnEventProviderManager.instance();
+
+ /**
+ * Constructor
+ *
+ * @param widget
+ * Time graph widget to which this button refers
+ */
+ public EventSeriesMenuButton(TimeGraphWidget widget) {
+ setText(Messages.eventSeriesMenuButtonName);
+
+ MenuItem separator = new SeparatorMenuItem();
+
+ /*
+ * There are minimum 3 items in the menu. Whenever there are more (from
+ * registered providers), make the separator visible.
+ */
+ getItems().addListener((ListChangeListener<MenuItem>) change -> {
+ separator.visibleProperty().set(change.getList().size() > 3);
+ });
+
+ /* "Create New Series" menu item */
+ MenuItem addNewSeriesMenuItem = new MenuItem(Messages.newEventSeriesMenuItem);
+ addNewSeriesMenuItem.setOnAction(e -> {
+ ITimeGraphModelProvider modelProvider = widget.getControl().getModelRenderProvider();
+ CreateEventSeriesDialog dialog = new CreateEventSeriesDialog(modelProvider);
+ dialog.setOnShowing(h -> Platform.runLater(() -> JfxUtils.centerDialogOnScreen(dialog, EventSeriesMenuButton.this)));
+ Optional<@Nullable PredicateDrawnEventProvider> results = dialog.showAndWait();
+ ITimeGraphDrawnEventProvider provider = results.orElse(null);
+ if (provider != null) {
+ PROVIDER_MANAGER.getRegisteredProviders().add(provider);
+ provider.enabledProperty().set(true);
+ }
+ });
+
+ /* "Clear series" menu item */
+ MenuItem clearSeriesMenuItem = new MenuItem(Messages.clearEventSeriesMenuItem);
+ clearSeriesMenuItem.setOnAction(e -> {
+ // TODO Eventually we could track which providers were created from
+ // this button/dialog, and only clear those here.
+ PROVIDER_MANAGER.getRegisteredProviders().clear();
+ });
+
+ getItems().addAll(separator, addNewSeriesMenuItem, clearSeriesMenuItem);
+
+
+ /* Load the already-registered providers */
+ PROVIDER_MANAGER.getRegisteredProviders().forEach(this::addProviderToMenu);
+
+ /* Watch for future addition/removal of providers */
+ PROVIDER_MANAGER.getRegisteredProviders().addListener((SetChangeListener<ITimeGraphDrawnEventProvider>) change -> {
+ ITimeGraphDrawnEventProvider addedProvider = change.getElementAdded();
+ if (addedProvider != null) {
+ addProviderToMenu(addedProvider);
+ }
+ ITimeGraphDrawnEventProvider removedProvider = change.getElementRemoved();
+ if (removedProvider != null) {
+ removeProviderFromMenu(removedProvider);
+ }
+ });
+ }
+
+ private void addProviderToMenu(ITimeGraphDrawnEventProvider provider) {
+ CheckMenuItem item = new EventProviderMenuItem(provider);
+ int index = getItems().size() - 3;
+ getItems().add(index, item);
+ }
+
+ private void removeProviderFromMenu(ITimeGraphDrawnEventProvider provider) {
+ MenuItem itemToRemove = Iterables
+ .tryFind(getItems(), item -> {
+ return (item instanceof EventProviderMenuItem
+ && ((EventProviderMenuItem) item).getProvider().equals(provider));
+ })
+ .orNull();
+
+ if (itemToRemove != null) {
+ getItems().remove(itemToRemove);
+ }
+ }
+
+ /**
+ * Menu item that represents a particular
+ * {@link ITimeGraphDrawnEventProvider}.
+ */
+ private static class EventProviderMenuItem extends CheckMenuItem {
+
+ private final ITimeGraphDrawnEventProvider fProvider;
+
+ public EventProviderMenuItem(ITimeGraphDrawnEventProvider provider) {
+ fProvider = provider;
+
+ TimeGraphDrawnEventSeries series = provider.getEventSeries();
+
+ setMnemonicParsing(false);
+ setText(series.getSeriesName());
+ selectedProperty().bindBidirectional(provider.enabledProperty());
+
+ Shape graphic = TimeGraphDrawnEventLayer.getShapeFromSymbol(series.getSymbolStyle().get());
+ Color color = JfxColorFactory.getColorFromDef(series.getColor().get());
+ graphic.setFill(color);
+ setGraphic(graphic);
+ }
+
+ public ITimeGraphDrawnEventProvider getProvider() {
+ return fProvider;
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc.
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.toolbar.drawnevents;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * Message bundle for the package
+ *
+ * @noreference Messages class
+ */
+@NonNullByDefault({})
+@SuppressWarnings("javadoc")
+public class Messages extends NLS {
+
+ private static final String BUNDLE_NAME = Messages.class.getPackage().getName() + ".messages"; //$NON-NLS-1$
+
+ public static String eventSeriesMenuButtonName;
+
+ public static String newEventSeriesMenuItem;
+ public static String clearEventSeriesMenuItem;
+
+ public static String createEventSeriesDialogTitle;
+ public static String createEventSeriesDialogSectionFilterDef;
+ public static String createEventSeriesDialogFieldEventName;
+ public static String createEventSeriesDialogSectionSymbolDef;
+ public static String createEventSeriesDialogFieldColor;
+ public static String createEventSeriesDialogFieldShape;
+
+ static {
+ NLS.initializeMessages(BUNDLE_NAME, Messages.class);
+ }
+
+ private Messages() {
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.toolbar.drawnevents;
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.FutureTask;
+import java.util.function.Predicate;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.tmf.core.event.ITmfEvent;
+import org.eclipse.tracecompass.tmf.core.request.ITmfEventRequest;
+import org.eclipse.tracecompass.tmf.core.request.ITmfEventRequest.ExecutionType;
+import org.eclipse.tracecompass.tmf.core.request.TmfEventRequest;
+import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
+import org.lttng.scope.tmf2.views.core.TimeRange;
+import org.lttng.scope.tmf2.views.core.context.ViewGroupContext;
+import org.lttng.scope.tmf2.views.core.timegraph.model.provider.ITimeGraphModelProvider;
+import org.lttng.scope.tmf2.views.core.timegraph.model.provider.drawnevents.TimeGraphDrawnEventProvider;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.TimeGraphEvent;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.drawnevents.TimeGraphDrawnEvent;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.drawnevents.TimeGraphDrawnEventRender;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.drawnevents.TimeGraphDrawnEventSeries;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.tree.TimeGraphTreeElement;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.tree.TimeGraphTreeRender;
+
+import com.google.common.collect.ImmutableList;
+
+class PredicateDrawnEventProvider extends TimeGraphDrawnEventProvider {
+
+ /** Maximum number of matching events */
+ private static final int MAX = 2000;
+
+ private final ITimeGraphModelProvider fModelProvider;
+ private final Predicate<ITmfEvent> fPredicate;
+
+ public PredicateDrawnEventProvider(TimeGraphDrawnEventSeries drawnEventSeries,
+ ITimeGraphModelProvider modelProvider,
+ Predicate<ITmfEvent> predicate) {
+ super(drawnEventSeries);
+ fModelProvider = modelProvider;
+ fPredicate = predicate;
+
+ /* Just use whatever trace is currently active */
+ traceProperty().bind(ViewGroupContext.getCurrent().currentTraceProperty());
+ }
+
+ @Override
+ public TimeGraphDrawnEventRender getEventRender(TimeGraphTreeRender treeRender,
+ TimeRange timeRange, @Nullable FutureTask<?> task) {
+
+ /* Very TMF-specific */
+ ITmfTrace trace = traceProperty().get();
+ if (trace == null) {
+ return new TimeGraphDrawnEventRender(timeRange, Collections.EMPTY_LIST);
+ }
+
+ long startIndex = 0;
+
+ List<ITmfEvent> traceEvents = new LinkedList<>();
+ TmfEventRequest req = new TmfEventRequest(ITmfEvent.class,
+ timeRange.toTmfTimeRange(),
+ startIndex,
+ ITmfEventRequest.ALL_DATA,
+ ExecutionType.BACKGROUND) {
+
+ private int matches = 0;
+
+ @Override
+ public void handleData(ITmfEvent event) {
+ super.handleData(event);
+ if (task != null && task.isCancelled()) {
+ cancel();
+ }
+
+ if (fPredicate.test(event)) {
+ matches++;
+ traceEvents.add(event);
+ if (matches > MAX) {
+ done();
+ }
+ }
+ }
+ };
+ trace.sendRequest(req);
+ try {
+ req.waitForCompletion();
+ } catch (InterruptedException e) {
+ }
+
+ if (req.isCancelled()) {
+ return new TimeGraphDrawnEventRender(timeRange, Collections.EMPTY_LIST);
+ }
+
+ List<TimeGraphDrawnEvent> drawnEvents = traceEvents.stream()
+ /* trace event -> TimeGraphEvent */
+ .map(traceEvent -> {
+ long timestamp = traceEvent.getTimestamp().toNanos();
+ /*
+ * Find the matching tree element for this trace event, if
+ * there is one.
+ */
+ Optional<TimeGraphTreeElement> treeElem = treeRender.getAllTreeElements().stream()
+ .filter(elem -> {
+ Predicate<ITmfEvent> predicate = elem.getEventMatching();
+ if (predicate == null) {
+ return false;
+ }
+ return predicate.test(traceEvent);
+ })
+ .findFirst();
+
+ if (!treeElem.isPresent()) {
+ return null;
+ }
+ return new TimeGraphEvent(timestamp, treeElem.get());
+ })
+ .filter(Objects::nonNull)
+ /* TimeGraphEvent -> TimeGraphDrawnEvent */
+ .map(tgEvent -> new TimeGraphDrawnEvent(tgEvent, getEventSeries(), null))
+ .collect(ImmutableList.toImmutableList());
+
+ return new TimeGraphDrawnEventRender(timeRange, drawnEvents);
+ }
+
+}
--- /dev/null
+###############################################################################
+# Copyright (c) 2017 EfficiOS Inc.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Eclipse Public License v1.0
+# which accompanies this distribution, and is available at
+# http://www.eclipse.org/legal/epl-v10.html
+###############################################################################
+
+eventSeriesMenuButtonName = Event Series
+
+newEventSeriesMenuItem = New Event Series...
+clearEventSeriesMenuItem = Clear All
+
+createEventSeriesDialogTitle = Create New Event Series
+createEventSeriesDialogSectionFilterDef = Filter Definition
+createEventSeriesDialogFieldEventName = Event Name
+createEventSeriesDialogSectionSymbolDef = Symbol
+createEventSeriesDialogFieldColor = Color
+createEventSeriesDialogFieldShape = Shape
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+@org.eclipse.jdt.annotation.NonNullByDefault
+package org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.toolbar.drawnevents;
--- /dev/null
+###############################################################################
+# Copyright (c) 2017 EfficiOS Inc.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Eclipse Public License v1.0
+# which accompanies this distribution, and is available at
+# http://www.eclipse.org/legal/epl-v10.html
+###############################################################################
+
+arrowSeriesMenuButtonName = Arrow Series
+
+sfFilterModeMenuButtonName = Filter Modes
+sfSortingModeMenuButtonName = Sorting Modes
+
+sfZoomInActionDescription = Zoom In
+sfZoomOutActionDescription = Zoom Out
+sfZoomToFullRangeActionDescription = Fit to Full Trace Range
+sfZoomToSelectionActionDescription = Fit to Selection
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc.
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.toolbar.modelconfig;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * Message bundle for the package
+ *
+ * @noreference Messages class
+ */
+@NonNullByDefault({})
+@SuppressWarnings("javadoc")
+public class Messages extends NLS {
+
+ private static final String BUNDLE_NAME = Messages.class.getPackage().getName() + ".messages"; //$NON-NLS-1$
+
+ public static String modelConfigButtonName;
+ public static String modelConfigDialogTitle;
+ public static String modelConfigDialogHeader;
+
+ public static String modelConfigDialogRowHeaderState;
+ public static String modelConfigDialogRowHeaderColor;
+ public static String modelConfigDialogRowHeaderLineThickness;
+
+ public static String modelConfigDialogResetDefaultsButton;
+
+ static {
+ NLS.initializeMessages(BUNDLE_NAME, Messages.class);
+ }
+
+ private Messages() {
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.toolbar.modelconfig;
+
+import org.lttng.scope.tmf2.views.ui.jfx.JfxImageFactory;
+import org.lttng.scope.tmf2.views.ui.jfx.JfxUtils;
+import org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.TimeGraphWidget;
+
+import javafx.scene.control.Button;
+import javafx.scene.control.Dialog;
+import javafx.scene.control.Tooltip;
+import javafx.scene.image.Image;
+import javafx.scene.image.ImageView;
+
+/**
+ * Button to open the legend mapping states to colors.
+ *
+ * @author Alexandre Montplaisir
+ */
+public class ModelConfigButton extends Button {
+
+ private static final String LEGEND_ICON_PATH = "/icons/toolbar/legend.gif"; //$NON-NLS-1$
+
+ /**
+ * Constructor
+ *
+ * @param widget
+ * The time graph widget to which this toolbar button is
+ * associated.
+ */
+ public ModelConfigButton(TimeGraphWidget widget) {
+ Image icon = JfxImageFactory.instance().getImageFromResource(LEGEND_ICON_PATH);
+ setGraphic(new ImageView(icon));
+ setTooltip(new Tooltip(Messages.modelConfigButtonName));
+
+ setOnAction(e -> {
+ Dialog<?> dialog = new ModelConfigDialog(widget);
+ dialog.show();
+ JfxUtils.centerDialogOnScreen(dialog, ModelConfigButton.this);
+ });
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.toolbar.modelconfig;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.lttng.scope.tmf2.views.core.config.ConfigOption;
+import org.lttng.scope.tmf2.views.core.timegraph.model.provider.states.ITimeGraphModelStateProvider;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.ColorDefinition;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.LineThickness;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.StateDefinition;
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.states.MultiStateInterval;
+import org.lttng.scope.tmf2.views.ui.jfx.CountingGridPane;
+import org.lttng.scope.tmf2.views.ui.jfx.JfxColorFactory;
+import org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.StateRectangle;
+import org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.TimeGraphWidget;
+
+import javafx.beans.property.ReadOnlyProperty;
+import javafx.event.ActionEvent;
+import javafx.scene.Node;
+import javafx.scene.control.ButtonBar.ButtonData;
+import javafx.scene.control.ButtonType;
+import javafx.scene.control.ColorPicker;
+import javafx.scene.control.Dialog;
+import javafx.scene.control.Label;
+import javafx.scene.control.MenuButton;
+import javafx.scene.control.RadioMenuItem;
+import javafx.scene.control.ToggleGroup;
+import javafx.scene.paint.Color;
+import javafx.scene.paint.Paint;
+import javafx.scene.shape.Rectangle;
+import javafx.scene.text.Text;
+
+class ModelConfigDialog extends Dialog<@Nullable Void> {
+
+ private static final double H_GAP = 10;
+
+ public ModelConfigDialog(TimeGraphWidget widget) {
+ setTitle(Messages.modelConfigDialogTitle);
+ setHeaderText(Messages.modelConfigDialogHeader);
+
+ ButtonType resetToDefaultButtonType = new ButtonType(Messages.modelConfigDialogResetDefaultsButton, ButtonData.LEFT);
+ getDialogPane().getButtonTypes().addAll(resetToDefaultButtonType, ButtonType.CLOSE);
+
+ // TODO Allow configuring arrow, etc. providers too (different tabs?)
+ ITimeGraphModelStateProvider stateProvider = widget.getControl().getModelRenderProvider().getStateProvider();
+ List<ColorDefControl> stateControls = stateProvider.getStateDefinitions().stream()
+ .map(stateDef -> new ColorDefControl(widget, stateDef))
+ .collect(Collectors.toList());
+
+ /* Setup the GridPane which will be the contents of the dialog */
+ CountingGridPane grid = new CountingGridPane();
+ grid.setHgap(H_GAP);
+ /* Header row */
+ grid.appendRow(new Text(Messages.modelConfigDialogRowHeaderState),
+ new Text(Messages.modelConfigDialogRowHeaderColor),
+ new Text(Messages.modelConfigDialogRowHeaderLineThickness));
+
+ stateControls.forEach(setter -> grid.appendRow(setter.getNodes()));
+
+ /* Add an "empty row", then the control for multi-state intervals */
+ LineThicknessMenuButton multiStateThicknessButton = new LineThicknessMenuButton(widget,
+ MultiStateInterval.MULTI_STATE_THICKNESS_OPTION,
+ widget.getDebugOptions().multiStatePaint);
+ grid.appendRow(new Text("")); //$NON-NLS-1$
+ grid.appendRow(new Label("Multi-states"), new Text(), multiStateThicknessButton);
+
+ getDialogPane().setContent(grid);
+
+ /*
+ * We do not set the dialog's 'resultConverter', there is nothing
+ * special to do on close (all changes are immediately valid).
+ */
+
+ /* Define how to "Reset Defaults" button works */
+ getDialogPane().lookupButton(resetToDefaultButtonType).addEventFilter(ActionEvent.ACTION, e -> {
+ /*
+ * This button should not close the dialog. Consuming the event here
+ * will prevent the dialog from closing.
+ */
+ e.consume();
+
+ stateControls.forEach(sc -> {
+ sc.getOptions().forEach(ConfigOption::resetToDefault);
+ sc.load();
+ });
+ multiStateThicknessButton.getOption().resetToDefault();
+ multiStateThicknessButton.load();
+
+ repaintAllRectangles(widget);
+ });
+
+ }
+
+ // TODO Repaint the rectangles of the whole Timeline view, not just 1 widget
+ private static void repaintAllRectangles(TimeGraphWidget widget) {
+ widget.getRenderedStateRectangles().forEach(StateRectangle::updatePaint);
+ }
+
+ private static class ColorDefControl {
+
+ private final TimeGraphWidget fWidget;
+ private final StateDefinition fStateDef;
+
+ private final Label fLabel;
+ private final ColorPicker fColorPicker;
+ private final LineThicknessMenuButton fLineThicknessButton;
+
+ /**
+ * Constructor
+ *
+ * TODO Use a "PaintPicker" dialog instead, so a full Paint object can
+ * be configured from the UI. There is apparently one in SceneBuilder.
+ */
+ public ColorDefControl(TimeGraphWidget widget, StateDefinition stateDef) {
+ fWidget = widget;
+ fStateDef = stateDef;
+
+ fLabel = new Label(stateDef.getName());
+ fColorPicker = new ColorPicker();
+ fColorPicker.getStyleClass().add(ColorPicker.STYLE_CLASS_BUTTON);
+ fLineThicknessButton = new LineThicknessMenuButton(widget, stateDef.getLineThickness(), fColorPicker.valueProperty());
+ load();
+
+ /*
+ * Whenever a new color is selected in the UI, update the
+ * corresponding model color.
+ */
+ fColorPicker.setOnAction(e -> {
+ Color color = fColorPicker.getValue();
+ if (color == null) {
+ return;
+ }
+ ColorDefinition colorDef = JfxColorFactory.colorToColorDef(color);
+ fStateDef.getColor().set(colorDef);
+
+ repaintAllRectangles(widget);
+ });
+
+ }
+
+ public Node[] getNodes() {
+ return new Node[] { fLabel, fColorPicker, fLineThicknessButton };
+ }
+
+ public Iterable<ConfigOption<?>> getOptions() {
+ return Arrays.asList(fStateDef.getColor(), fStateDef.getLineThickness());
+ }
+
+ public void load() {
+ Color color = JfxColorFactory.getColorFromDef(fStateDef.getColor().get());
+ fColorPicker.setValue(color);
+
+ fLineThicknessButton.load();
+ }
+ }
+
+ private static class LineThicknessMenuButton extends MenuButton {
+
+ private static final double RECTANGLE_WIDTH = 30;
+
+ private final ConfigOption<LineThickness> fOption;
+ private final ReadOnlyProperty<? extends Paint> fColorSource;
+
+ public LineThicknessMenuButton(TimeGraphWidget widget,
+ ConfigOption<LineThickness> option,
+ ReadOnlyProperty<? extends Paint> colorSource) {
+ fOption = option;
+ fColorSource = colorSource;
+
+ ToggleGroup tg = new ToggleGroup();
+ List<LineThicknessMenuButtonItem> items = Arrays.stream(LineThickness.values())
+ .map(lt -> {
+ LineThicknessMenuButtonItem rmi = new LineThicknessMenuButtonItem(lt);
+ rmi.setGraphic(getRectangleForThickness(lt));
+ rmi.setToggleGroup(tg);
+
+ LineThickness currentThickness = option.get();
+ rmi.setSelected(lt == currentThickness);
+
+ rmi.setOnAction(e -> {
+ option.set(lt);
+ LineThicknessMenuButton.this.setGraphic(getRectangleForThickness(lt));
+ repaintAllRectangles(widget);
+ });
+ return rmi;
+ })
+ .collect(Collectors.toList());
+
+ /* Initial value shown in the button */
+ setGraphic(getRectangleForThickness(option.get()));
+ getItems().addAll(items);
+ }
+
+ private Rectangle getRectangleForThickness(LineThickness lt) {
+ Rectangle rectangle = new Rectangle(RECTANGLE_WIDTH, StateRectangle.getHeightFromThickness(lt));
+ rectangle.fillProperty().bind(fColorSource);
+ return rectangle;
+ }
+
+ public ConfigOption<LineThickness> getOption() {
+ return fOption;
+ }
+
+ public void load() {
+ LineThickness lt = fOption.get();
+ setGraphic(getRectangleForThickness(lt));
+ getItems().stream()
+ .map(item -> (LineThicknessMenuButtonItem) item)
+ .filter(item -> item.getLineThickness() == lt)
+ .findFirst().get().setSelected(true);
+ }
+ }
+
+ private static class LineThicknessMenuButtonItem extends RadioMenuItem {
+
+ private final LineThickness fLineThickness;
+
+ public LineThicknessMenuButtonItem(LineThickness lt) {
+ fLineThickness = lt;
+ }
+
+ public LineThickness getLineThickness() {
+ return fLineThickness;
+ }
+ }
+
+}
\ No newline at end of file
--- /dev/null
+###############################################################################
+# Copyright (c) 2017 EfficiOS Inc.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Eclipse Public License v1.0
+# which accompanies this distribution, and is available at
+# http://www.eclipse.org/legal/epl-v10.html
+###############################################################################
+
+modelConfigButtonName = Model Configuration
+modelConfigDialogTitle = Model Configuration
+modelConfigDialogHeader = State Model Configuration
+
+modelConfigDialogRowHeaderState = State
+modelConfigDialogRowHeaderColor = Color
+modelConfigDialogRowHeaderLineThickness = Line Thickness
+
+modelConfigDialogResetDefaultsButton = Reset Defaults
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+@org.eclipse.jdt.annotation.NonNullByDefault
+package org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.toolbar.modelconfig;
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc.
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.toolbar.nav;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * Message bundle for the package
+ *
+ * @noreference Messages class
+ */
+@NonNullByDefault({})
+@SuppressWarnings("javadoc")
+public class Messages extends NLS {
+
+ private static final String BUNDLE_NAME = Messages.class.getPackage().getName() + ".messages"; //$NON-NLS-1$
+
+ public static String sfFollowStateChangesNavModeName;
+ public static String sfFollowEventsNavModeName;
+ public static String sfFollowArrowsNavModeName;
+ public static String sfFollowBookmarksNavModeName;
+
+ public static String sfNextEventJobName;
+ public static String sfPreviousEventJobName;
+
+ static {
+ NLS.initializeMessages(BUNDLE_NAME, Messages.class);
+ }
+
+ private Messages() {
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.toolbar.nav;
+
+import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimeRange;
+import org.eclipse.tracecompass.tmf.core.trace.TmfTraceManager;
+import org.lttng.scope.tmf2.views.core.TimeRange;
+import org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.TimeGraphWidget;
+
+/**
+ * Common utilities for navigation actions.
+ *
+ * @author Alexandre Montplaisir
+ */
+final class NavUtils {
+
+ private NavUtils() {}
+
+ /**
+ * Move the selection to the target timestamp. Also update the visible range
+ * to be centered on that timestamp, but only if it is outside of the
+ * current visible range.
+ *
+ * This should only be called when reaching the new timestamp is caused by a
+ * user action (ie, not simply because another view sent a signal).
+ *
+ * @param viewer
+ * The viewer on which to work
+ * @param timestamp
+ * The timestamp to select, and potentially move to
+ */
+ public static void selectNewTimestamp(TimeGraphWidget viewer, long timestamp) {
+ /* Update the selection to the new timestamp. */
+ viewer.getControl().updateTimeRangeSelection(TimeRange.of(timestamp, timestamp));
+
+ TimeRange fullTimeGraphRange = viewer.getControl().getViewContext().getCurrentTraceFullRange();
+ TmfTimeRange windowRange = TmfTraceManager.getInstance().getCurrentTraceContext().getWindowRange();
+ long windowStart = windowRange.getStartTime().toNanos();
+ long windowEnd = windowRange.getEndTime().toNanos();
+ if (windowStart <= timestamp && timestamp <= windowEnd) {
+ /* Timestamp is still in the visible range, don't touch anything. */
+ return;
+ }
+ /* Update the visible range to the requested timestamp. */
+ /* The "span" of the window (aka zoom level) will remain constant. */
+ long windowSpan = windowEnd - windowStart;
+ if (windowSpan > fullTimeGraphRange.getDuration()) {
+ /* Should never happen, but just to be mathematically safe. */
+ windowSpan = fullTimeGraphRange.getDuration();
+ }
+
+ long newStart = timestamp - (windowSpan / 2);
+ long newEnd = newStart + windowSpan;
+
+ /* Clamp the range to the borders of the pane/trace. */
+ if (newStart < fullTimeGraphRange.getStart()) {
+ newStart = fullTimeGraphRange.getStart();
+ newEnd = newStart + windowSpan;
+ } else if (newEnd > fullTimeGraphRange.getEnd()) {
+ newEnd = fullTimeGraphRange.getEnd();
+ newStart = newEnd - windowSpan;
+ }
+
+ viewer.getControl().updateVisibleTimeRange(TimeRange.of(newStart, newEnd), true);
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.toolbar.nav;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.TimeGraphWidget;
+
+import com.google.common.collect.ImmutableList;
+
+import javafx.scene.control.Button;
+import javafx.scene.control.MenuButton;
+import javafx.scene.control.RadioMenuItem;
+import javafx.scene.control.ToggleGroup;
+import javafx.scene.image.ImageView;
+import javafx.scene.layout.HBox;
+
+/**
+ * Class encapsulating the fowards/backwards navigation buttons, with support
+ * for different "modes" of navigation.
+ *
+ * @author Alexandre Montplaisir
+ */
+public class NavigationButtons {
+
+ // ------------------------------------------------------------------------
+ // Inner classes
+ // ------------------------------------------------------------------------
+
+ private static class BackButton extends Button {
+
+ private final TimeGraphWidget fViewer;
+
+ public BackButton(TimeGraphWidget viewer, NavigationMode initialNavMode) {
+ fViewer = viewer;
+ setNavMode(initialNavMode);
+ }
+
+ public void setNavMode(NavigationMode navMode) {
+ setGraphic(new ImageView(navMode.getBackIcon()));
+ setOnAction(e -> {
+ navMode.navigateBackwards(fViewer);
+ });
+ }
+ }
+
+ private static class ForwardButton extends Button {
+
+ private final TimeGraphWidget fViewer;
+
+ public ForwardButton(TimeGraphWidget viewer, NavigationMode initialNavMode) {
+ fViewer = viewer;
+ setNavMode(initialNavMode);
+ }
+
+ public void setNavMode(NavigationMode navMode) {
+ setGraphic(new ImageView(navMode.getForwardIcon()));
+ setOnAction(e -> {
+ navMode.navigateForwards(fViewer);
+ });
+ }
+ }
+
+ // ------------------------------------------------------------------------
+ // Pre-defined navigation modes
+ // ------------------------------------------------------------------------
+
+ private static final List<NavigationMode> NAVIGATION_MODES = ImmutableList.of(
+ new NavigationModeFollowStateChanges(),
+ new NavigationModeFollowEvents(),
+ new NavigationModeFollowArrows(),
+ new NavigationModeFollowBookmarks());
+
+ // ------------------------------------------------------------------------
+ // Class components
+ // ------------------------------------------------------------------------
+
+ private final Button fBackButton;
+ private final Button fForwardButton;
+ private final MenuButton fMenuButton;
+
+ /**
+ * Constructor. This will prepare the buttons, which can then be retrieved
+ * with {@link #getBackButton()}, {@link #getForwardButton()} and
+ * {@link #getMenuButton()}.
+ *
+ * @param viewer
+ * The viewer to which the buttons will belong
+ */
+ public NavigationButtons(TimeGraphWidget viewer) {
+ List<NavigationMode> navModes = NAVIGATION_MODES;
+ NavigationMode initialMode = navModes.get(0);
+
+ BackButton backButton = new BackButton(viewer, initialMode);
+ ForwardButton forwardButton = new ForwardButton(viewer, initialMode);
+ MenuButton menuButton = new MenuButton();
+
+ ToggleGroup tg = new ToggleGroup();
+ List<RadioMenuItem> items = NAVIGATION_MODES.stream()
+ .map(nm -> {
+ RadioMenuItem item = new RadioMenuItem();
+ item.setText(nm.getModeName());
+ item.setGraphic(new HBox(new ImageView(nm.getBackIcon()), new ImageView(nm.getForwardIcon())));
+ item.setToggleGroup(tg);
+ item.setOnAction(e -> {
+ backButton.setNavMode(nm);
+ forwardButton.setNavMode(nm);
+ });
+ item.setDisable(!nm.isEnabled());
+ return item;
+ })
+ .collect(Collectors.toList());
+
+ items.get(0).setSelected(true);
+ menuButton.getItems().addAll(items);
+
+ fBackButton = backButton;
+ fForwardButton = forwardButton;
+ fMenuButton = menuButton;
+ }
+
+ /**
+ * Get the "back" button.
+ *
+ * @return The back button
+ */
+ public Button getBackButton() {
+ return fBackButton;
+ }
+
+ /**
+ * Get the "forward" button.
+ *
+ * @return The forward button
+ */
+ public Button getForwardButton() {
+ return fForwardButton;
+ }
+
+ /**
+ * Get the drop-down menu button, which allows switching between the
+ * available navigation modes.
+ *
+ * @return The drop-down menu button
+ */
+ public MenuButton getMenuButton() {
+ return fMenuButton;
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.toolbar.nav;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.lttng.scope.tmf2.views.ui.jfx.JfxImageFactory;
+import org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.TimeGraphWidget;
+
+import javafx.scene.image.Image;
+
+/**
+ * Abstract class for defining navigation (back and forward buttons) modes in
+ * the toolbar.
+ *
+ * @author Alexandre Montplaisir
+ */
+public abstract class NavigationMode {
+
+ private final String fModeName;
+ private final @Nullable Image fBackIcon;
+ private final @Nullable Image fForwardIcon;
+
+ /**
+ * Constructor
+ *
+ * @param modeName
+ * Name of the mode, will be shown in the UI
+ * @param backIconPath
+ * The icon to use for the "back" button
+ * @param forwardIconPath
+ * The icon to use for the "forward" button
+ */
+ public NavigationMode(String modeName, String backIconPath, String forwardIconPath) {
+ fModeName = modeName;
+
+ JfxImageFactory factory = JfxImageFactory.instance();
+ fBackIcon = factory.getImageFromResource(backIconPath);
+ fForwardIcon = factory.getImageFromResource(forwardIconPath);
+ }
+
+ /**
+ * Get the name of this navigation mode.
+ *
+ * @return The mode name
+ */
+ public String getModeName() {
+ return fModeName;
+ }
+
+ /**
+ * Get the icon to use for the "back" button.
+ *
+ * @return The back button icon
+ */
+ public @Nullable Image getBackIcon() {
+ return fBackIcon;
+ }
+
+ /**
+ * Get the icon to use for the "forward" button.
+ *
+ * @return The forward button icon
+ */
+ public @Nullable Image getForwardIcon() {
+ return fForwardIcon;
+ }
+
+ /**
+ * If for some reason this particular mode should not be available to the
+ * user, this should be overridden to return 'false'.
+ *
+ * @return If this mode is enabled
+ */
+ public boolean isEnabled() {
+ return true;
+ }
+
+ /**
+ * What to do when the back button is invoked while in this mode.
+ *
+ * @param viewer
+ * The viewer on which we are working
+ */
+ public abstract void navigateBackwards(TimeGraphWidget viewer);
+
+ /**
+ * What to do when the forward button is invoked while in this mode.
+ *
+ * @param viewer
+ * The viewer on which we are working
+ */
+ public abstract void navigateForwards(TimeGraphWidget viewer);
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.toolbar.nav;
+
+import static java.util.Objects.requireNonNull;
+
+import org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.TimeGraphWidget;
+
+/**
+ * Navigation mode using drawn arrows.
+ *
+ * If we are currently positioned at an arrow endpoint, it follow the arrow to
+ * its other endpoint, scrolling vertically if needed.
+ *
+ * If we are not at an arrow endpoint, following the current entry backwards or
+ * forwards until we find one. If we don't find any, do nothing.
+ *
+ * @author Alexandre Montplaisir
+ */
+public class NavigationModeFollowArrows extends NavigationMode {
+
+ private static final String BACK_ICON_PATH = "/icons/toolbar/nav_arrow_back.gif"; //$NON-NLS-1$
+ private static final String FWD_ICON_PATH = "/icons/toolbar/nav_arrow_fwd.gif"; //$NON-NLS-1$
+
+ /**
+ * Constructor
+ */
+ public NavigationModeFollowArrows() {
+ super(requireNonNull(Messages.sfFollowArrowsNavModeName),
+ BACK_ICON_PATH,
+ FWD_ICON_PATH);
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return false;
+ }
+
+ @Override
+ public void navigateBackwards(TimeGraphWidget viewer) {
+ // TODO NYI
+ System.out.println("Follow arrows backwards");
+ }
+
+ @Override
+ public void navigateForwards(TimeGraphWidget viewer) {
+ // TODO NYI
+ System.out.println("Follow arrows forwards");
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.toolbar.nav;
+
+import static java.util.Objects.requireNonNull;
+
+import org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.TimeGraphWidget;
+
+/**
+ * Navigation mode using the current entry's events. It looks through all events
+ * in the trace belonging to the tree entry of the current selected state, and
+ * navigates through them. This allows stopping at events that may not cause a
+ * state change shown in the view.
+ *
+ * @author Alexandre Montplaisir
+ */
+public class NavigationModeFollowBookmarks extends NavigationMode {
+
+ private static final String BACK_ICON_PATH = "/icons/toolbar/nav_bookmark_back.gif"; //$NON-NLS-1$
+ private static final String FWD_ICON_PATH = "/icons/toolbar/nav_bookmark_fwd.gif"; //$NON-NLS-1$
+
+ /**
+ * Constructor
+ */
+ public NavigationModeFollowBookmarks() {
+ super(requireNonNull(Messages.sfFollowBookmarksNavModeName),
+ BACK_ICON_PATH,
+ FWD_ICON_PATH);
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return false;
+ }
+
+ @Override
+ public void navigateBackwards(TimeGraphWidget viewer) {
+ // TODO NYI
+ System.out.println("Follow bookmarks backwards");
+ }
+
+ @Override
+ public void navigateForwards(TimeGraphWidget viewer) {
+ // TODO NYI
+ System.out.println("Follow bookmarks forwards");
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.toolbar.nav;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.function.Predicate;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.jobs.ISchedulingRule;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.tracecompass.tmf.core.event.ITmfEvent;
+import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimestamp;
+import org.eclipse.tracecompass.tmf.core.trace.ITmfContext;
+import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
+import org.eclipse.tracecompass.tmf.core.trace.TmfTraceManager;
+import org.eclipse.tracecompass.tmf.core.trace.TmfTraceUtils;
+import org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.StateRectangle;
+import org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.TimeGraphWidget;
+
+/**
+ * Navigation mode using the current entry's events. It looks through all events
+ * in the trace belonging to the tree entry of the current selected state, and
+ * navigates through them. This allows stopping at events that may not cause a
+ * state change shown in the view.
+ *
+ * @author Alexandre Montplaisir
+ */
+public class NavigationModeFollowEvents extends NavigationMode {
+
+ private static final String BACK_ICON_PATH = "/icons/toolbar/nav_event_back.gif"; //$NON-NLS-1$
+ private static final String FWD_ICON_PATH = "/icons/toolbar/nav_event_fwd.gif"; //$NON-NLS-1$
+
+ /**
+ * Mutex rule for search action jobs, making sure they execute sequentially
+ */
+ private final ISchedulingRule fSearchActionMutexRule = new ISchedulingRule() {
+ @Override
+ public boolean isConflicting(@Nullable ISchedulingRule rule) {
+ return (rule == this);
+ }
+
+ @Override
+ public boolean contains(@Nullable ISchedulingRule rule) {
+ return (rule == this);
+ }
+ };
+
+ /**
+ * Constructor
+ */
+ public NavigationModeFollowEvents() {
+ super(requireNonNull(Messages.sfFollowEventsNavModeName),
+ BACK_ICON_PATH,
+ FWD_ICON_PATH);
+ }
+
+ @Override
+ public void navigateBackwards(TimeGraphWidget viewer) {
+ navigate(viewer, false);
+ }
+
+ @Override
+ public void navigateForwards(TimeGraphWidget viewer) {
+ navigate(viewer, true);
+ }
+
+ private void navigate(TimeGraphWidget viewer, boolean forward) {
+ StateRectangle state = viewer.getSelectedState();
+ ITmfTrace trace = viewer.getControl().getViewContext().getCurrentTrace();
+ if (state == null || trace == null) {
+ return;
+ }
+ Predicate<ITmfEvent> predicate = state.getStateInterval().getTreeElement().getEventMatching();
+ if (predicate == null) {
+ /* The tree element does not support navigating by events. */
+ return;
+ }
+
+ String jobName = (forward ? Messages.sfNextEventJobName : Messages.sfPreviousEventJobName);
+
+ Job job = new Job(jobName) {
+ @Override
+ protected IStatus run(@Nullable IProgressMonitor monitor) {
+ long currentTime = TmfTraceManager.getInstance().getCurrentTraceContext().getSelectionRange().getStartTime().toNanos();
+ ITmfContext ctx = trace.seekEvent(TmfTimestamp.fromNanos(currentTime));
+ long rank = ctx.getRank();
+ ctx.dispose();
+
+ ITmfEvent event = (forward ?
+ TmfTraceUtils.getNextEventMatching(trace, rank, predicate, monitor) :
+ TmfTraceUtils.getPreviousEventMatching(trace, rank, predicate, monitor));
+ if (event != null) {
+ NavUtils.selectNewTimestamp(viewer, event.getTimestamp().toNanos());
+ }
+ return Status.OK_STATUS;
+ }
+ };
+
+ /*
+ * Make subsequent jobs not run concurrently, but wait after one
+ * another.
+ */
+ job.setRule(fSearchActionMutexRule);
+ job.schedule();
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.toolbar.nav;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.Comparator;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.lttng.scope.tmf2.views.core.timegraph.model.render.tree.TimeGraphTreeElement;
+import org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.StateRectangle;
+import org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.TimeGraphWidget;
+
+/**
+ * Navigation mode using state changes. It goes to the end/start of the current
+ * selected state interval, or jumps to the next/previous one if we are already
+ * at a border.
+ *
+ * @author Alexandre Montplaisir
+ */
+public class NavigationModeFollowStateChanges extends NavigationMode {
+
+ private static final String BACK_ICON_PATH = "/icons/toolbar/nav_statechange_back.gif"; //$NON-NLS-1$
+ private static final String FWD_ICON_PATH = "/icons/toolbar/nav_statechange_fwd.gif"; //$NON-NLS-1$
+
+ private static final Comparator<StateRectangle> EARLIEST_START_TIME_COMPARATOR =
+ Comparator.comparingLong(rect -> rect.getStateInterval().getStartEvent().getTimestamp());
+ private static final Comparator<StateRectangle> LATEST_END_TIME_COMPARATOR =
+ Comparator.<StateRectangle> comparingLong(rect -> rect.getStateInterval().getEndEvent().getTimestamp()).reversed();
+
+ /**
+ * Constructor
+ */
+ public NavigationModeFollowStateChanges() {
+ super(requireNonNull(Messages.sfFollowStateChangesNavModeName),
+ BACK_ICON_PATH,
+ FWD_ICON_PATH);
+ }
+
+ @Override
+ public void navigateBackwards(TimeGraphWidget viewer) {
+ navigate(viewer, false);
+ }
+
+
+ @Override
+ public void navigateForwards(TimeGraphWidget viewer) {
+ navigate(viewer, true);
+ }
+
+ private static void navigate(TimeGraphWidget viewer, boolean forward) {
+ StateRectangle state = viewer.getSelectedState();
+ if (state == null) {
+ return;
+ }
+ long stateStartTime = state.getStateInterval().getStartEvent().getTimestamp();
+ long stateEndTime = state.getStateInterval().getEndEvent().getTimestamp();
+
+ /* Aim to go to the start/end of the next/previous interval */
+ long targetTimestamp = (forward ? stateEndTime + 1 : stateStartTime - 1);
+ TimeGraphTreeElement treeElement = state.getStateInterval().getStartEvent().getTreeElement();
+ List<StateRectangle> potentialStates = getPotentialStates(viewer, targetTimestamp, treeElement, forward);
+
+ if (potentialStates.isEmpty()) {
+ /*
+ * We either reached the end of our model or an edge of the trace.
+ * Go to the end/start of the current state.
+ */
+ long bound = (forward ? stateEndTime : stateStartTime);
+ NavUtils.selectNewTimestamp(viewer, bound);
+ return;
+ }
+
+ /*
+ * Also compute the intervals that intersect the target timestamp.
+ * We will prefer those, but if there aren't any, we'll pick the
+ * best "potential" state.
+ */
+ List<StateRectangle> intersectingStates = getIntersectingStates(potentialStates, targetTimestamp, forward);
+
+ StateRectangle newState;
+ if (intersectingStates.isEmpty()) {
+ /*
+ * Let's look back into 'potentialStates' (non-intersecting)
+ * and pick the interval with the closest bound.
+ */
+ Optional<StateRectangle> optState = getBestPotentialState(potentialStates, forward);
+ if (!optState.isPresent()) {
+ /* We did our best and didn't find anything. */
+ return;
+ }
+ newState = optState.get();
+
+ } else if (intersectingStates.size() == 1) {
+ /* There is only one match, must be the right one. */
+ newState = intersectingStates.get(0);
+ } else {
+ /*
+ * There is more than one match (overlapping intervals, can
+ * happen sometimes with multi-states). Pick the one with the
+ * earliest start time (for backwards) or latest end time (for
+ * forwards), to ensure we "move out" on the next action.
+ */
+ newState = intersectingStates.stream()
+ .sorted(forward ? LATEST_END_TIME_COMPARATOR : EARLIEST_START_TIME_COMPARATOR)
+ .findFirst().get();
+ }
+
+ viewer.setSelectedState(newState, true);
+ newState.showTooltip(forward);
+ NavUtils.selectNewTimestamp(viewer, targetTimestamp);
+ }
+
+ /**
+ * Compute all the potentially valid states for a navigate
+ * backwards/forwards operation.
+ *
+ * This means all the states for the given time graph tree element which
+ * happen before, or after, the time given timestamp for backwards or
+ * forwards operation respectively.
+ */
+ private static List<StateRectangle> getPotentialStates(TimeGraphWidget viewer, long targetTimestamp,
+ TimeGraphTreeElement treeElement, boolean forward) {
+
+ Stream<StateRectangle> potentialStates = viewer.getRenderedStateRectangles().stream()
+ /* Keep only the intervals of the current tree element */
+ .filter(rect -> rect.getStateInterval().getStartEvent().getTreeElement().equals(treeElement));
+
+ if (forward) {
+ /*
+ * Keep only those intersecting, or happening after, the target
+ * timestamp.
+ */
+ potentialStates = potentialStates.filter(rect -> rect.getStateInterval().getEndEvent().getTimestamp() >= targetTimestamp);
+ } else {
+ /*
+ * Keep only those intersecting, or happening before, the target
+ * timestamp.
+ */
+ potentialStates = potentialStates.filter(rect -> rect.getStateInterval().getStartEvent().getTimestamp() <= targetTimestamp);
+ }
+
+ List<StateRectangle> allStates = potentialStates.collect(Collectors.toList());
+ return allStates;
+ }
+
+ /**
+ * From a list of potential states, generate the list of intersecting
+ * states. This means all state intervals that actually cross the target
+ * timestamp.
+ *
+ * Note that we've already verified one of the start/end time for back/forth
+ * navigation when generating the potential states, this method only needs
+ * to check the other bound.
+ */
+ private static List<StateRectangle> getIntersectingStates(List<StateRectangle> potentialStates,
+ long targetTimestamp, boolean forward) {
+
+ Stream<StateRectangle> intersectingStates = potentialStates.stream();
+ if (forward) {
+ intersectingStates = intersectingStates.filter(rect -> {
+ long start = rect.getStateInterval().getStartEvent().getTimestamp();
+ return (targetTimestamp >= start);
+ });
+ } else {
+ intersectingStates = intersectingStates.filter(rect -> {
+ long end = rect.getStateInterval().getEndEvent().getTimestamp();
+ return (targetTimestamp <= end);
+ });
+ }
+ return intersectingStates.collect(Collectors.toList());
+ }
+
+ private static Optional<StateRectangle> getBestPotentialState(List<StateRectangle> potentialStates, boolean forward) {
+ return potentialStates.stream()
+ .sorted(forward ? EARLIEST_START_TIME_COMPARATOR : LATEST_END_TIME_COMPARATOR)
+ .findFirst();
+ }
+
+}
--- /dev/null
+###############################################################################
+# Copyright (c) 2017 EfficiOS Inc.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Eclipse Public License v1.0
+# which accompanies this distribution, and is available at
+# http://www.eclipse.org/legal/epl-v10.html
+###############################################################################
+
+sfFollowStateChangesNavModeName = Follow State Changes
+sfFollowEventsNavModeName = Follow Events
+sfFollowArrowsNavModeName = Follow Arrows
+sfFollowBookmarksNavModeName = Follow Bookmarks
+
+sfNextEventJobName = Searching for next matching event
+sfPreviousEventJobName = Searching for previous matching event
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+@org.eclipse.jdt.annotation.NonNullByDefault
+package org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.toolbar.nav;
--- /dev/null
+/*
+ * Copyright (C) 2017 EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+
+@org.eclipse.jdt.annotation.NonNullByDefault
+package org.lttng.scope.tmf2.views.ui.timeline.widgets.timegraph.toolbar;