Merge pull request #136 from informatik-ag-ngl/f/javafx

Replaced Swing with JavaFX
This commit is contained in:
Kai S. K. Engelbart 2020-06-09 20:25:49 +00:00 committed by GitHub
commit 30567ed1ef
73 changed files with 1968 additions and 4339 deletions

5
.gitignore vendored
View File

@ -1 +1,4 @@
/target/
/target/
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*

View File

@ -128,3 +128,364 @@ org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
org.eclipse.jdt.core.compiler.release=disabled
org.eclipse.jdt.core.compiler.source=11
org.eclipse.jdt.core.formatter.align_assignment_statements_on_columns=true
org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines=1
org.eclipse.jdt.core.formatter.align_type_members_on_columns=true
org.eclipse.jdt.core.formatter.align_variable_declarations_on_columns=true
org.eclipse.jdt.core.formatter.align_with_spaces=false
org.eclipse.jdt.core.formatter.alignment_for_additive_operator=16
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=84
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=80
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=20
org.eclipse.jdt.core.formatter.alignment_for_assignment=0
org.eclipse.jdt.core.formatter.alignment_for_bitwise_operator=16
org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
org.eclipse.jdt.core.formatter.alignment_for_compact_loops=16
org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80
org.eclipse.jdt.core.formatter.alignment_for_conditional_expression_chain=0
org.eclipse.jdt.core.formatter.alignment_for_enum_constants=16
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_logical_operator=16
org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0
org.eclipse.jdt.core.formatter.alignment_for_module_statements=16
org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
org.eclipse.jdt.core.formatter.alignment_for_multiplicative_operator=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_relational_operator=0
org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=80
org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=84
org.eclipse.jdt.core.formatter.alignment_for_shift_operator=0
org.eclipse.jdt.core.formatter.alignment_for_string_concatenation=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_last_class_body_declaration=0
org.eclipse.jdt.core.formatter.blank_lines_after_package=1
org.eclipse.jdt.core.formatter.blank_lines_before_abstract_method=1
org.eclipse.jdt.core.formatter.blank_lines_before_field=0
org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=1
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_statement_group_in_switch=0
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.align_tags_descriptions_grouped=true
org.eclipse.jdt.core.formatter.comment.align_tags_names_descriptions=false
org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=true
org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
org.eclipse.jdt.core.formatter.comment.count_line_length_from_starting_position=true
org.eclipse.jdt.core.formatter.comment.format_block_comments=true
org.eclipse.jdt.core.formatter.comment.format_header=true
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=false
org.eclipse.jdt.core.formatter.comment.indent_root_tags=false
org.eclipse.jdt.core.formatter.comment.indent_tag_description=false
org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
org.eclipse.jdt.core.formatter.comment.insert_new_line_between_different_tags=do not insert
org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not 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=true
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=true
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=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_space_after_additive_operator=insert
org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
org.eclipse.jdt.core.formatter.insert_space_after_arrow_in_switch_case=insert
org.eclipse.jdt.core.formatter.insert_space_after_arrow_in_switch_default=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_bitwise_operator=insert
org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=do not 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_switch_case_expressions=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_logical_operator=insert
org.eclipse.jdt.core.formatter.insert_space_after_multiplicative_operator=insert
org.eclipse.jdt.core.formatter.insert_space_after_not_operator=do not 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_relational_operator=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_shift_operator=insert
org.eclipse.jdt.core.formatter.insert_space_after_string_concatenation=insert
org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_additive_operator=insert
org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
org.eclipse.jdt.core.formatter.insert_space_before_arrow_in_switch_case=insert
org.eclipse.jdt.core.formatter.insert_space_before_arrow_in_switch_default=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=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_bitwise_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_switch_case_expressions=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_logical_operator=insert
org.eclipse.jdt.core.formatter.insert_space_before_multiplicative_operator=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_relational_operator=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_shift_operator=insert
org.eclipse.jdt.core.formatter.insert_space_before_string_concatenation=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=false
org.eclipse.jdt.core.formatter.join_wrapped_lines=true
org.eclipse.jdt.core.formatter.keep_annotation_declaration_on_one_line=one_line_if_single_item
org.eclipse.jdt.core.formatter.keep_anonymous_type_declaration_on_one_line=one_line_never
org.eclipse.jdt.core.formatter.keep_code_block_on_one_line=one_line_if_empty
org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=true
org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
org.eclipse.jdt.core.formatter.keep_enum_constant_declaration_on_one_line=one_line_never
org.eclipse.jdt.core.formatter.keep_enum_declaration_on_one_line=one_line_never
org.eclipse.jdt.core.formatter.keep_if_then_body_block_on_one_line=one_line_if_single_item
org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false
org.eclipse.jdt.core.formatter.keep_lambda_body_block_on_one_line=one_line_always
org.eclipse.jdt.core.formatter.keep_loop_body_block_on_one_line=one_line_if_empty
org.eclipse.jdt.core.formatter.keep_method_body_on_one_line=one_line_if_single_item
org.eclipse.jdt.core.formatter.keep_simple_do_while_body_on_same_line=false
org.eclipse.jdt.core.formatter.keep_simple_for_body_on_same_line=false
org.eclipse.jdt.core.formatter.keep_simple_getter_setter_on_one_line=true
org.eclipse.jdt.core.formatter.keep_simple_while_body_on_same_line=false
org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=true
org.eclipse.jdt.core.formatter.keep_type_declaration_on_one_line=one_line_never
org.eclipse.jdt.core.formatter.lineSplit=150
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_after_code_block=0
org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_code_block=0
org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
org.eclipse.jdt.core.formatter.number_of_blank_lines_at_end_of_code_block=0
org.eclipse.jdt.core.formatter.number_of_blank_lines_at_end_of_method_body=0
org.eclipse.jdt.core.formatter.number_of_blank_lines_before_code_block=0
org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1
org.eclipse.jdt.core.formatter.parentheses_positions_in_annotation=separate_lines_if_wrapped
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=tab
org.eclipse.jdt.core.formatter.tabulation.size=4
org.eclipse.jdt.core.formatter.text_block_indentation=0
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_additive_operator=true
org.eclipse.jdt.core.formatter.wrap_before_assignment_operator=false
org.eclipse.jdt.core.formatter.wrap_before_bitwise_operator=true
org.eclipse.jdt.core.formatter.wrap_before_conditional_operator=true
org.eclipse.jdt.core.formatter.wrap_before_logical_operator=true
org.eclipse.jdt.core.formatter.wrap_before_multiplicative_operator=true
org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true
org.eclipse.jdt.core.formatter.wrap_before_relational_operator=true
org.eclipse.jdt.core.formatter.wrap_before_shift_operator=true
org.eclipse.jdt.core.formatter.wrap_before_string_concatenation=true
org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true
org.eclipse.jdt.core.javaFormatter=org.eclipse.jdt.core.defaultJavaFormatter

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,3 @@
default.configuration=
eclipse.preferences.version=1
hibernate3.enabled=false

12
pom.xml
View File

@ -28,7 +28,17 @@
<dependency>
<groupId>com.github.informatik-ag-ngl</groupId>
<artifactId>envoy-common</artifactId>
<version>develop-SNAPSHOT</version>
<version>f~groups-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>11.0.2</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-fxml</artifactId>
<version>11.0.2</version>
</dependency>
</dependencies>

View File

@ -1,177 +0,0 @@
package envoy.client;
import java.awt.EventQueue;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import envoy.client.data.*;
import envoy.client.net.Client;
import envoy.client.net.WriteProxy;
import envoy.client.ui.StatusTrayIcon;
import envoy.client.ui.container.ChatWindow;
import envoy.client.ui.container.LoginDialog;
import envoy.data.Config;
import envoy.data.Message;
import envoy.data.User.UserStatus;
import envoy.exception.EnvoyException;
import envoy.util.EnvoyLog;
/**
* Starts the Envoy client and prompts the user to enter their name.<br>
* <br>
* Project: <strong>envoy-client</strong><br>
* File: <strong>Startup.java</strong><br>
* Created: <strong>12 Oct 2019</strong><br>
*
* @author Leon Hofmeister
* @author Maximilian K&auml;fer
* @author Kai S. K. Engelbart
* @since Envoy Client v0.1-alpha
*/
public class Startup {
private static ChatWindow chatWindow;
private static final Logger logger = EnvoyLog.getLogger(Startup.class);
/**
* Loads the application by first loading the configuration, then acquiring a
* user name and connecting to the server. If the server cannot be reached,
* offline mode is entered if possible. After that, a {@link ChatWindow}
* instance is initialized and then displayed to the user. Upon application
* exit, settings and the local database are saved.
*
* @param args the command line arguments may contain configuration parameters
* and are parsed by the {@link Config} class
* @since Envoy Client v0.1-alpha
*/
public static void main(String[] args) {
ClientConfig config = ClientConfig.getInstance();
SwingUtilities.invokeLater(() -> chatWindow = new ChatWindow());
try {
// Load the configuration from client.properties first
Properties properties = new Properties();
properties.load(Startup.class.getClassLoader().getResourceAsStream("client.properties"));
config.load(properties);
// Override configuration values with command line arguments
if (args.length > 0) config.load(args);
// Check if all mandatory configuration values have been initialized
if (!config.isInitialized()) throw new EnvoyException("Configuration is not fully initialized");
} catch (Exception e) {
JOptionPane.showMessageDialog(null, "Error loading configuration values:\n" + e, "Configuration error", JOptionPane.ERROR_MESSAGE);
e.printStackTrace();
System.exit(1);
}
// Setup logger for the envoy package
EnvoyLog.initialize(config);
EnvoyLog.attach("envoy");
EnvoyLog.setFileLevelBarrier(config.getFileLevelBarrier());
EnvoyLog.setConsoleLevelBarrier(config.getConsoleLevelBarrier());
// Initialize the local database
LocalDB localDB;
if (config.isIgnoreLocalDB()) {
localDB = new TransientLocalDB();
JOptionPane.showMessageDialog(null,
"Ignoring local database.\nMessages will not be saved!",
"Local database warning",
JOptionPane.WARNING_MESSAGE);
} else try {
localDB = new PersistentLocalDB(new File(config.getHomeDirectory(), config.getLocalDB().getPath()));
} catch (IOException e3) {
logger.log(Level.SEVERE, "Could not initialize local database", e3);
JOptionPane.showMessageDialog(null, "Could not initialize local database!\n" + e3, "Local database error", JOptionPane.ERROR_MESSAGE);
System.exit(1);
return;
}
// Initialize client and unread message cache
Client client = new Client();
Cache<Message> cache = new Cache<>();
// Try to connect to the server
new LoginDialog(client, localDB, cache);
SwingUtilities.invokeLater(() -> chatWindow.setVisible(true));
// Set client user in local database
localDB.setUser(client.getSender());
// Initialize chats in local database
try {
localDB.initializeUserStorage();
localDB.loadUserData();
} catch (FileNotFoundException e) {
// The local database file has not yet been created, probably first login
} catch (Exception e) {
e.printStackTrace();
JOptionPane.showMessageDialog(null,
"Error while loading local database: " + e + "\nChats will not be stored locally.",
"Local DB error",
JOptionPane.WARNING_MESSAGE);
}
// Initialize write proxy
final WriteProxy writeProxy = client.createWriteProxy(localDB);
if (client.isOnline()) {
// Save all users to the local database and flush cache
localDB.setUsers(client.getUsers());
writeProxy.flushCache();
} else
// Set all contacts to offline mode
localDB.getUsers().values().stream().filter(u -> u != localDB.getUser()).forEach(u -> u.setStatus(UserStatus.OFFLINE));
// Display ChatWindow and StatusTrayIcon
EventQueue.invokeLater(() -> {
try {
chatWindow.initContent(client, localDB, writeProxy);
// Relay unread messages from cache
if (cache != null && client.isOnline()) cache.relay();
try {
new StatusTrayIcon(chatWindow).show();
// If the tray icon is supported and corresponding settings is set, hide the
// chat window on close
Settings.getInstance()
.getItems()
.get("onCloseMode")
.setChangeHandler((onCloseMode) -> chatWindow
.setDefaultCloseOperation((Boolean) onCloseMode ? JFrame.HIDE_ON_CLOSE : JFrame.EXIT_ON_CLOSE));
} catch (EnvoyException e) {
logger.warning("The StatusTrayIcon is not supported on this platform!");
}
} catch (Exception e) {
e.printStackTrace();
}
});
// Save Settings and PersistentLocalDB on shutdown
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
try {
logger.info("Closing connection...");
client.close();
logger.info("Saving local database and settings...");
localDB.save();
Settings.getInstance().save();
} catch (Exception e) {
logger.log(Level.SEVERE, "Unable to save local files", e);
}
}));
}
}

View File

@ -2,12 +2,12 @@ package envoy.client.data;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import envoy.client.net.WriteProxy;
import envoy.client.ui.list.Model;
import envoy.data.Message;
import envoy.data.*;
import envoy.data.Message.MessageStatus;
import envoy.data.User;
import envoy.event.MessageStatusChangeEvent;
/**
@ -23,12 +23,12 @@ import envoy.event.MessageStatusChangeEvent;
* @author Kai S. K. Engelbart
* @since Envoy Client v0.1-alpha
*/
public class Chat implements Serializable {
public final class Chat implements Serializable {
private static final long serialVersionUID = 0L;
private final Contact recipient;
private final List<Message> messages = new ArrayList<>();
private final User recipient;
private final Model<Message> model = new Model<>();
private static final long serialVersionUID = 1L;
/**
* Provides the list of messages that the recipient receives.<br>
@ -37,15 +37,10 @@ public class Chat implements Serializable {
* @param recipient the user who receives the messages
* @since Envoy Client v0.1-alpha
*/
public Chat(User recipient) { this.recipient = recipient; }
public Chat(Contact recipient) { this.recipient = recipient; }
/**
* Appends a message to the bottom of this chat
*
* @param message the message to append
* @since Envoy Client v0.1-alpha
*/
public void appendMessage(Message message) { model.add(message); }
@Override
public String toString() { return String.format("Chat[recipient=%s,messages=%d]", recipient, messages.size()); }
/**
* Sets the status of all chat messages received from the recipient to
@ -59,8 +54,8 @@ public class Chat implements Serializable {
* @since Envoy Client v0.3-alpha
*/
public void read(WriteProxy writeProxy) throws IOException {
for (int i = model.size() - 1; i >= 0; --i) {
final Message m = model.get(i);
for (int i = messages.size() - 1; i >= 0; --i) {
final Message m = messages.get(i);
if (m.getSenderID() == recipient.getID()) if (m.getStatus() == MessageStatus.READ) break;
else {
m.setStatus(MessageStatus.READ);
@ -74,17 +69,29 @@ public class Chat implements Serializable {
* the status {@code READ}
* @since Envoy Client v0.3-alpha
*/
public boolean isUnread() { return !model.isEmpty() && model.get(model.size() - 1).getStatus() != MessageStatus.READ; }
public boolean isUnread() { return !messages.isEmpty() && messages.get(messages.size() - 1).getStatus() != MessageStatus.READ; }
/**
* @return all messages in the current chat
* @since Envoy Client v0.1-alpha
* @since Envoy Client v0.1-beta
*/
public Model<Message> getModel() { return model; }
public List<Message> getMessages() { return messages; }
/**
* @return the recipient of a message
* @since Envoy Client v0.1-alpha
*/
public User getRecipient() { return recipient; }
public Contact getRecipient() { return recipient; }
/**
* @return whether this {@link Chat} points at a {@link User}
* @since Envoy Client v0.1-beta
*/
public boolean isUserChat() { return recipient instanceof User; }
/**
* @return whether this {@link Chat} points at a {@link Group}
* @since Envoy Client v0.1-beta
*/
public boolean isGroupChat() { return recipient instanceof Group; }
}

View File

@ -1,7 +1,6 @@
package envoy.client.data;
import java.io.File;
import java.security.NoSuchAlgorithmException;
import java.util.function.Function;
import java.util.logging.Level;
@ -110,11 +109,5 @@ public class ClientConfig extends Config {
* the registration option
* @since Envoy Client v0.3-alpha
*/
public LoginCredentials getLoginCredentials() {
try {
return new LoginCredentials(getUser(), getPassword(), false);
} catch (NoSuchAlgorithmException e) {
return null;
}
}
public LoginCredentials getLoginCredentials() { return new LoginCredentials(getUser(), getPassword(), false); }
}

View File

@ -2,10 +2,10 @@ package envoy.client.data;
import java.util.*;
import envoy.data.IDGenerator;
import envoy.data.Message;
import envoy.data.User;
import envoy.data.*;
import envoy.event.GroupResizeEvent;
import envoy.event.MessageStatusChangeEvent;
import envoy.event.NameChangeEvent;
/**
* Stores information about the current {@link User} and their {@link Chat}s.
@ -21,7 +21,7 @@ import envoy.event.MessageStatusChangeEvent;
public abstract class LocalDB {
protected User user;
protected Map<String, User> users = new HashMap<>();
protected Map<String, Contact> users = new HashMap<>();
protected List<Chat> chats = new ArrayList<>();
protected IDGenerator idGenerator;
protected Cache<Message> messageCache = new Cache<>();
@ -71,12 +71,12 @@ public abstract class LocalDB {
* user names as keys
* @since Envoy Client v0.2-alpha
*/
public Map<String, User> getUsers() { return users; }
public Map<String, Contact> getUsers() { return users; }
/**
* @param users the users to set
*/
public void setUsers(Map<String, User> users) { this.users = users; }
public void setUsers(Map<String, Contact> users) { this.users = users; }
/**
* @return all saved {@link Chat} objects that list the client user as the
@ -153,9 +153,41 @@ public abstract class LocalDB {
* @since Envoy Client v0.1-beta
*/
public Message getMessage(long id) {
for (Chat c : chats)
for (Message m : c.getModel())
if (m.getID() == id) return m;
return null;
return chats.stream().map(Chat::getMessages).flatMap(List::stream).filter(m -> m.getID() == id).findAny().orElse(null);
}
/**
* Performs a contact name change if the corresponding contact is present.
*
* @param event the {@link NameChangeEvent} to process
* @since Envoy Client v0.1-beta
*/
public void replaceContactName(NameChangeEvent event) {
chats.stream().map(Chat::getRecipient).filter(c -> c.getID() == event.getID()).findAny().ifPresent(c -> c.setName(event.get()));
}
/**
* Performs a group resize operation if the corresponding group is present.
*
* @param event the {@link GroupResizeEvent} to process
* @since Envoy Client v0.1-beta
*/
public void updateGroup(GroupResizeEvent event) {
chats.stream()
.map(Chat::getRecipient)
.filter(Group.class::isInstance)
.filter(g -> g.getID() == event.getGroupID() && g.getID() != user.getID())
.map(Group.class::cast)
.findAny()
.ifPresent(group -> {
switch (event.getOperation()) {
case ADD:
group.getContacts().add(event.get());
break;
case REMOVE:
group.getContacts().remove(event.get());
break;
}
});
}
}

View File

@ -40,18 +40,18 @@ public class PersistentLocalDB extends LocalDB {
* Constructs an empty local database. To serialize any chats to the file
* system, call {@link PersistentLocalDB#initializeUserStorage()}.
*
* @param localDbDir the directory in which to store users and chats
* @param localDBDir the directory in which to store users and chats
* @throws IOException if the PersistentLocalDB could not be initialized
* @since Envoy Client v0.1-alpha
*/
public PersistentLocalDB(File localDbDir) throws IOException {
localDBDir = localDbDir;
public PersistentLocalDB(File localDBDir) throws IOException {
this.localDBDir = localDBDir;
// Initialize local database directory
if (localDbDir.exists() && !localDbDir.isDirectory())
throw new IOException(String.format("LocalDbDir '%s' is not a directory!", localDbDir.getAbsolutePath()));
usersFile = new File(localDbDir, "users.db");
idGeneratorFile = new File(localDbDir, "id_generator.db");
if (localDBDir.exists() && !localDBDir.isDirectory())
throw new IOException(String.format("LocalDBDir '%s' is not a directory!", localDBDir.getAbsolutePath()));
usersFile = new File(localDBDir, "users.db");
idGeneratorFile = new File(localDBDir, "id_generator.db");
}
/**

View File

@ -6,8 +6,6 @@ import java.util.HashMap;
import java.util.Map;
import java.util.prefs.Preferences;
import envoy.client.ui.Color;
import envoy.client.ui.Theme;
import envoy.util.SerializationUtils;
/**
@ -28,26 +26,20 @@ public class Settings {
// Actual settings accessible by the rest of the application
private Map<String, SettingsItem<?>> items;
private Map<String, Theme> themes;
/**
* Settings are stored in this file.
*/
private static final File settingsFile = new File(ClientConfig.getInstance().getHomeDirectory(), "settings.ser");
/**
* User-defined themes are stored inside this file.
*/
private static final File themeFile = new File(ClientConfig.getInstance().getHomeDirectory(), "themes.ser");
/**
* Singleton instance of this class.
*/
private static Settings settings = new Settings();
/**
* The way to instantiate the settings.
* Is set to private to deny other instances of that object.
* The way to instantiate the settings. Is set to private to deny other
* instances of that object.
*
* @since Envoy Client v0.2-alpha
*/
@ -59,22 +51,6 @@ public class Settings {
items = new HashMap<>();
}
supplementDefaults();
// Load themes from theme file
try {
themes = SerializationUtils.read(themeFile, HashMap.class);
} catch (ClassNotFoundException | IOException e1) {
themes = new HashMap<>();
setCurrentTheme("dark");
}
// Load standard themes not defined in the themes file
themes.put("dark",
new Theme("dark", Color.black, Color.darkGray, Color.white, new Color(165, 60, 232), Color.white, Color.orange, Color.blue,
Color.white, Color.white));
themes.put("light",
new Theme("light", new Color(235, 235, 235), Color.white, Color.white, Color.darkGray, Color.black, Color.orange, Color.darkGray,
Color.black, Color.black));
}
/**
@ -88,46 +64,29 @@ public class Settings {
/**
* Updates the preferences when the save button is clicked.
*
* @throws IOException if an error occurs while saving the themes to the theme
* file
* @throws IOException if an error occurs while saving the themes
* @since Envoy Client v0.2-alpha
*/
public void save() throws IOException {
// Save settings to settings file
SerializationUtils.write(settingsFile, items);
// Save themes to theme file
SerializationUtils.write(themeFile, themes);
}
private void supplementDefaults() {
items.putIfAbsent("enterToSend", new SettingsItem<>(true, "Enter to send", "Sends a message by pressing the enter key."));
items.putIfAbsent("onCloseMode", new SettingsItem<>(true, "Hide on close", "Hides the chat window when it is closed."));
items.putIfAbsent("currentTheme", new SettingsItem<>("dark", null));
items.putIfAbsent("currentTheme", new SettingsItem<>("dark", "Current Theme Name", "The name of the currently selected theme."));
}
/**
* Adds new theme to the theme map.
*
* @param theme the {@link Theme} to add
* @return the name of the currently active theme
* @since Envoy Client v0.2-alpha
*/
public void addNewThemeToMap(Theme theme) { getThemes().put(theme.getThemeName(), theme); }
public String getCurrentTheme() { return (String) items.get("currentTheme").get(); }
/**
* @return the name of the currently active {@link Theme}
* @since Envoy Client v0.2-alpha
*/
public String getCurrentThemeName() { return (String) items.get("currentTheme").get(); }
/**
* @return the currently active {@link Theme}
* @since Envoy Client v0.1-beta
*/
public Theme getCurrentTheme() { return getTheme(getCurrentThemeName()); }
/**
* Sets the name of the current {@link Theme}.
* Sets the name of the current theme.
*
* @param themeName the name to set
* @since Envoy Client v0.2-alpha
@ -175,25 +134,4 @@ public class Settings {
* @param items the items to set
*/
public void setItems(Map<String, SettingsItem<?>> items) { this.items = items; }
/**
* @return a {@code Map<String, Theme>} of all themes with their names as keys
* @since Envoy Client v0.2-alpha
*/
public Map<String, Theme> getThemes() { return themes; }
/**
* Sets the {@code Map<String, Theme>} of all themes with their names as keys
*
* @param themes the theme map to set
* @since Envoy Client v0.2-alpha
*/
public void setThemes(Map<String, Theme> themes) { this.themes = themes; }
/**
* @param themeName the name of the {@link Theme} to get
* @return the {@link Theme} with the specified name
* @since Envoy Client v0.3-alpha
*/
public Theme getTheme(String themeName) { return themes.get(themeName); }
}

View File

@ -1,19 +1,15 @@
package envoy.client.data;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
import javax.swing.JComponent;
import envoy.client.ui.primary.PrimaryToggleSwitch;
/**
* Encapsulates a persistent value that is directly or indirectly mutable by the
* user.<br>
* <br>
* Project: <strong>envoy-clientChess</strong><br>
* Project: <strong>envoy-client</strong><br>
* File: <strong>SettingsItem.java</strong><br>
* Created: <strong>23.12.2019</strong><br>
*
@ -23,19 +19,12 @@ import envoy.client.ui.primary.PrimaryToggleSwitch;
*/
public class SettingsItem<T> implements Serializable {
private T value;
private Class<? extends JComponent> componentClass;
private String userFriendlyName, description;
private T value;
private String userFriendlyName, description;
transient private Consumer<T> changeHandler;
private transient Consumer<T> changeHandler;
private static final Map<Class<?>, Class<? extends JComponent>> componentClasses = new HashMap<>();
private static final long serialVersionUID = 0L;
static {
componentClasses.put(Boolean.class, PrimaryToggleSwitch.class);
}
private static final long serialVersionUID = 1L;
/**
* Initializes a {@link SettingsItem}. The default value's class will be mapped
@ -48,39 +37,11 @@ public class SettingsItem<T> implements Serializable {
* @since Envoy Client v0.3-alpha
*/
public SettingsItem(T value, String userFriendlyName, String description) {
this(value, componentClasses.get(value.getClass()));
this.value = value;
this.userFriendlyName = userFriendlyName;
this.description = description;
}
/**
* Initializes a {@link SettingsItem}. The default value's class will be mapped
* to a specific {@link JComponent}. The mapping can also be disables if this
* parameter is {@code null}. In that case a {@link NullPointerException} will
* be thrown if the method {@link SettingsItem#getComponent()} is called.
*
* @param value the default value
* @param componentClass the class of the {@link JComponent} to represent this
* {@link SettingsItem} with
* @since Envoy Client v0.3-alpha
*/
public SettingsItem(T value, Class<? extends JComponent> componentClass) {
this.value = value;
this.componentClass = componentClass;
}
/**
* @return an instance of the {@link JComponent} that represents this
* {@link SettingsItem}
* @throws ReflectiveOperationException if the component initialization failed
* @throws SecurityException if the component initialization failed
* @since Envoy Client v0.3-alpha
*/
public JComponent getComponent() throws ReflectiveOperationException, SecurityException {
if (componentClass == null) throw new NullPointerException("Component class is null");
return componentClass.getConstructor(SettingsItem.class).newInstance(this);
}
/**
* @return the value
* @since Envoy Client v0.3-alpha
@ -99,18 +60,6 @@ public class SettingsItem<T> implements Serializable {
this.value = value;
}
/**
* @return the componentClass
* @since Envoy Client v0.3-alpha
*/
public Class<? extends JComponent> getComponentClass() { return componentClass; }
/**
* @param componentClass the componentClass to set
* @since Envoy Client v0.3-alpha
*/
public void setComponentClass(Class<? extends JComponent> componentClass) { this.componentClass = componentClass; }
/**
* @return the userFriendlyName
* @since Envoy Client v0.3-alpha

View File

@ -1,18 +0,0 @@
package envoy.client.event;
import envoy.event.Event;
/**
* This {@link Event} indicates that a handshake was completed successfully.
*
* Project: <strong>envoy-client</strong><br>
* File: <strong>HandshakeSuccessfulEvent.java</strong><br>
* Created: <strong>8 Feb 2020</strong><br>
*
* @author Leon Hofmeister
* @since Envoy Client v0.3-alpha
*/
public class HandshakeSuccessfulEvent extends Event.Valueless {
private static final long serialVersionUID = 0L;
}

View File

@ -1,6 +1,5 @@
package envoy.client.event;
import envoy.client.ui.Theme;
import envoy.event.Event;
/**
@ -11,16 +10,16 @@ import envoy.event.Event;
* @author Kai S. K. Engelbart
* @since Envoy Client v0.2-alpha
*/
public class ThemeChangeEvent extends Event<Theme> {
public class ThemeChangeEvent extends Event<String> {
private static final long serialVersionUID = 0L;
/**
* Initializes a {@link ThemeChangeEvent} conveying information about the change
* of the {@link Theme} currently in use
* of the theme currently in use.
*
* @param theme the new currently used {@link Theme} object
* @param theme the name of the new theme
* @since Envoy Client v0.2-alpha
*/
public ThemeChangeEvent(Theme theme) { super(theme); }
public ThemeChangeEvent(String theme) { super(theme); }
}

View File

@ -5,17 +5,18 @@ import java.io.IOException;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeoutException;
import java.util.logging.Logger;
import javax.naming.TimeLimitExceededException;
import envoy.client.data.Cache;
import envoy.client.data.ClientConfig;
import envoy.client.data.LocalDB;
import envoy.client.event.SendEvent;
import envoy.data.*;
import envoy.event.*;
import envoy.event.ContactOperationEvent.Operation;
import envoy.event.contact.ContactOperationEvent;
import envoy.event.contact.ContactSearchResult;
import envoy.util.EnvoyLog;
import envoy.util.SerializationUtils;
@ -40,13 +41,14 @@ public class Client implements Closeable {
private boolean online;
// Asynchronously initialized during handshake
private volatile User sender;
private volatile Contacts contacts;
private volatile boolean rejected;
private volatile User sender;
private volatile Set<? extends Contact> contacts;
private volatile boolean rejected;
// Configuration and logging
private static final ClientConfig config = ClientConfig.getInstance();
private static final Logger logger = EnvoyLog.getLogger(Client.class);
// Configuration, logging and event management
private static final ClientConfig config = ClientConfig.getInstance();
private static final Logger logger = EnvoyLog.getLogger(Client.class);
private static final EventBus eventBus = EventBus.getInstance();
/**
* Enters the online mode by acquiring a user ID from the server. As a
@ -58,29 +60,27 @@ public class Client implements Closeable {
* @param receivedMessageCache a message cache containing all unread messages
* from the server that can be relayed after
* initialization
* @throws TimeLimitExceededException if the server could not be reached
* @throws IOException if the login credentials could not be
* written
* @throws InterruptedException if the current thread is interrupted while
* waiting for the handshake response
* @throws TimeoutException if the server could not be reached
* @throws IOException if the login credentials could not be
* written
* @throws InterruptedException if the current thread is interrupted while
* waiting for the handshake response
*/
public void performHandshake(LoginCredentials credentials, Cache<Message> receivedMessageCache)
throws TimeLimitExceededException, IOException, InterruptedException {
throws TimeoutException, IOException, InterruptedException {
if (online) throw new IllegalStateException("Handshake has already been performed successfully");
// Establish TCP connection
logger.info(String.format("Attempting connection to server %s:%d...", config.getServer(), config.getPort()));
logger.finer(String.format("Attempting connection to server %s:%d...", config.getServer(), config.getPort()));
socket = new Socket(config.getServer(), config.getPort());
logger.info("Successfully connected to server.");
logger.fine("Successfully established TCP connection to server");
// Create message receiver
// Create object receiver
receiver = new Receiver(socket.getInputStream());
// Register user creation processor, contact list processor and message cache
receiver.registerProcessor(User.class, sender -> this.sender = sender);
receiver.registerProcessor(Contacts.class, contacts -> this.contacts = contacts);
receiver.registerProcessor(User.class, sender -> { this.sender = sender; contacts = sender.getContacts(); });
receiver.registerProcessor(Message.class, receivedMessageCache);
receiver.registerProcessor(HandshakeRejectionEvent.class, evt -> { rejected = true; EventBus.getInstance().dispatch(evt); });
receiver.registerProcessor(HandshakeRejectionEvent.class, evt -> { rejected = true; eventBus.dispatch(evt); });
rejected = false;
@ -91,8 +91,8 @@ public class Client implements Closeable {
SerializationUtils.writeBytesWithLength(credentials, socket.getOutputStream());
// Wait for a maximum of five seconds to acquire the sender object
long start = System.currentTimeMillis();
while (sender == null || contacts == null) {
final long start = System.currentTimeMillis();
while (sender == null) {
// Quit immediately after handshake rejection
// This method can then be called again
@ -102,15 +102,16 @@ public class Client implements Closeable {
return;
}
if (System.currentTimeMillis() - start > 5000) throw new TimeLimitExceededException("Did not log in after 5 seconds");
if (System.currentTimeMillis() - start > 5000) throw new TimeoutException("Did not log in after 5 seconds");
Thread.sleep(500);
}
logger.info("Handshake completed.");
online = true;
// Remove user creation processor
// Remove all processors as they are only used during the handshake
receiver.removeAllProcessors();
logger.info("Handshake completed.");
}
/**
@ -145,17 +146,23 @@ public class Client implements Closeable {
// Process message ID generation
receiver.registerProcessor(IDGenerator.class, localDB::setIDGenerator);
// Process contact searches
receiver.registerProcessor(ContactSearchResult.class, EventBus.getInstance()::dispatch);
// Process name changes
receiver.registerProcessor(NameChangeEvent.class, evt -> { localDB.replaceContactName(evt); eventBus.dispatch(evt); });
receiver.registerProcessor(Contacts.class,
contacts -> EventBus.getInstance().dispatch(new ContactOperationEvent(contacts.getContacts().get(0), Operation.ADD)));
// Process contact searches
receiver.registerProcessor(ContactSearchResult.class, eventBus::dispatch);
// Process contact operations
receiver.registerProcessor(ContactOperationEvent.class, eventBus::dispatch);
// Process group size changes
receiver.registerProcessor(GroupResizeEvent.class, evt -> { localDB.updateGroup(evt); eventBus.dispatch(evt); });
// Send event
EventBus.getInstance().register(SendEvent.class, evt -> {
eventBus.register(SendEvent.class, evt -> {
try {
sendEvent(evt.get());
} catch (IOException e) {
} catch (final IOException e) {
e.printStackTrace();
}
});
@ -212,10 +219,11 @@ public class Client implements Closeable {
* user names as keys
* @since Envoy Client v0.2-alpha
*/
public Map<String, User> getUsers() {
public Map<String, Contact> getUsers() {
checkOnline();
Map<String, User> users = new HashMap<>();
contacts.getContacts().forEach(u -> users.put(u.getName(), u));
final Map<String, Contact> users = new HashMap<>();
contacts.forEach(u -> users.put(u.getName(), u));
users.put(sender.getName(), sender);
return users;
}
@ -224,7 +232,7 @@ public class Client implements Closeable {
private void writeObject(Object obj) throws IOException {
checkOnline();
logger.fine("Sending object " + obj);
logger.fine("Sending " + obj);
SerializationUtils.writeBytesWithLength(obj, socket.getOutputStream());
}
@ -239,10 +247,10 @@ public class Client implements Closeable {
/**
* Sets the client user which is used to send messages.
*
* @param sender the client user to set
* @param clientUser the client user to set
* @since Envoy Client v0.2-alpha
*/
public void setSender(User sender) { this.sender = sender; }
public void setSender(User clientUser) { this.sender = clientUser; }
/**
* @return the {@link Receiver} used by this {@link Client}
@ -259,11 +267,11 @@ public class Client implements Closeable {
* @return the contacts of this {@link Client}
* @since Envoy Client v0.3-alpha
*/
public Contacts getContacts() { return contacts; }
public Set<? extends Contact> getContacts() { return contacts; }
/**
* @param contacts the contacts to set
* @since Envoy Client v0.3-alpha
*/
public void setContacts(Contacts contacts) { this.contacts = contacts; }
public void setContacts(Set<? extends Contact> contacts) { this.contacts = contacts; }
}

View File

@ -14,6 +14,9 @@ import envoy.util.EnvoyLog;
import envoy.util.SerializationUtils;
/**
* Receives objects from the server and passes them to processor objects based
* on their class.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>Receiver.java</strong><br>
* Created: <strong>30.12.2019</strong><br>
@ -32,12 +35,19 @@ public class Receiver extends Thread {
* Creates an instance of {@link Receiver}.
*
* @param in the {@link InputStream} to parse objects from
* @since Envoy Client v0.3-alpha
*/
public Receiver(InputStream in) {
super("Receiver");
this.in = in;
}
/**
* Starts the receiver loop. When an object is read, it is passed to the
* appropriate processor.
*
* @since Envoy Client v0.3-alpha
*/
@Override
public void run() {
@ -54,7 +64,7 @@ public class Receiver extends Thread {
try (ObjectInputStream oin = new ObjectInputStream(new ByteArrayInputStream(objBytes))) {
Object obj = oin.readObject();
logger.fine("Received object " + obj);
logger.fine("Received " + obj);
// Get appropriate processor
@SuppressWarnings("rawtypes")
@ -65,7 +75,7 @@ public class Receiver extends Thread {
}
}
} catch (SocketException e) {
logger.info("Connection probably closed by client. Exiting receiver thread...");
// Connection probably closed by client.
} catch (Exception e) {
logger.log(Level.SEVERE, "Error on receiver thread", e);
e.printStackTrace();
@ -78,11 +88,14 @@ public class Receiver extends Thread {
*
* @param processorClass the object class accepted by the processor
* @param processor the object processor
* @since Envoy Client v0.3-alpha
*/
public <T> void registerProcessor(Class<T> processorClass, Consumer<T> processor) { processors.put(processorClass, processor); }
/**
* Removes all object processors registered at this {@link Receiver}.
*
* @since Envoy Client v0.3-alpha
*/
public void removeAllProcessors() { processors.clear(); }
}

View File

@ -3,6 +3,7 @@ package envoy.client.net;
import java.util.function.Consumer;
import envoy.client.data.LocalDB;
import envoy.data.User;
import envoy.event.EventBus;
import envoy.event.UserStatusChangeEvent;
@ -26,7 +27,7 @@ public class UserStatusChangeProcessor implements Consumer<UserStatusChangeEvent
@Override
public void accept(UserStatusChangeEvent evt) {
localDB.getUsers().values().stream().filter(u -> u.getID() == evt.getID()).findFirst().get().setStatus(evt.get());
localDB.getUsers().values().stream().filter(u -> u.getID() == evt.getID()).map(User.class::cast).findFirst().get().setStatus(evt.get());
EventBus.getInstance().dispatch(evt);
}
}

View File

@ -1,114 +0,0 @@
package envoy.client.ui;
import java.awt.color.ColorSpace;
/**
* This class further develops {@link java.awt.Color} by adding extra methods
* and more default colors.
*
* Project: <strong>envoy-client</strong><br>
* File: <strong>Color.java</strong><br>
* Created: <strong>23.12.2019</strong><br>
*
* @author Kai S. K. Engelbart
* @since Envoy Client v0.3-alpha
*/
@SuppressWarnings("javadoc")
public class Color extends java.awt.Color {
/**
* The color white. In the default sRGB space.
*/
public static final Color white = new Color(255, 255, 255);
/**
* The color light gray. In the default sRGB space.
*/
public static final Color lightGray = new Color(192, 192, 192);
/**
* The color gray. In the default sRGB space.
*/
public static final Color gray = new Color(128, 128, 128);
/**
* The color dark gray. In the default sRGB space.
*/
public static final Color darkGray = new Color(64, 64, 64);
/**
* The color black. In the default sRGB space.
*/
public static final Color black = new Color(0, 0, 0);
/**
* The color red. In the default sRGB space.
*/
public static final Color red = new Color(255, 0, 0);
/**
* The color pink. In the default sRGB space.
*/
public static final Color pink = new Color(255, 175, 175);
/**
* The color orange. In the default sRGB space.
*/
public static final Color orange = new Color(255, 200, 0);
/**
* The color yellow. In the default sRGB space.
*/
public static final Color yellow = new Color(255, 255, 0);
/**
* The color green. In the default sRGB space.
*/
public static final Color green = new Color(0, 255, 0);
/**
* The color magenta. In the default sRGB space.
*/
public static final Color magenta = new Color(255, 0, 255);
/**
* The color cyan. In the default sRGB space.
*/
public static final Color cyan = new Color(0, 255, 255);
/**
* The color blue. In the default sRGB space.
*/
public static final Color blue = new Color(0, 0, 255);
private static final long serialVersionUID = 0L;
public Color(java.awt.Color other) { this(other.getRGB()); }
public Color(int rgb) { super(rgb); }
public Color(int rgba, boolean hasalpha) { super(rgba, hasalpha); }
public Color(int r, int g, int b) { super(r, g, b); }
public Color(float r, float g, float b) { super(r, g, b); }
public Color(ColorSpace cspace, float[] components, float alpha) { super(cspace, components, alpha); }
public Color(int r, int g, int b, int a) { super(r, g, b, a); }
public Color(float r, float g, float b, float a) { super(r, g, b, a); }
/**
* @return the inversion of this {@link Color} by replacing the red, green and
* blue values by subtracting them form 255
* @since Envoy Client v0.3-alpha
*/
public Color invert() { return new Color(255 - getRed(), 255 - getGreen(), 255 - getBlue()); }
/**
* @return the hex value of this {@link Color}
* @since Envoy Client v0.3-alpha
*/
public String toHex() { return String.format("#%02x%02x%02x", getRed(), getGreen(), getBlue()); }
}

View File

@ -0,0 +1,60 @@
package envoy.client.ui;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import envoy.data.Contact;
import envoy.data.Group;
import envoy.data.User;
/**
* Project: <strong>envoy-client</strong><br>
* File: <strong>UserListCell.java</strong><br>
* Created: <strong>28.03.2020</strong><br>
*
* @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta
*/
public class ContactListCell extends ListCell<Contact> {
/**
* Displays the name of a contact. If the contact is a user, their online status
* is displayed as well.
*
* @since Envoy Client v0.1-beta
*/
@Override
protected void updateItem(Contact contact, boolean empty) {
super.updateItem(contact, empty);
if (!empty && contact != null) {
// the infoLabel displays specific contact info, i.e. status of a user or amount
// of members in a group
Label infoLabel = null;
if (contact instanceof User) {
// user specific info
infoLabel = new Label(((User) contact).getStatus().toString());
Color textColor = null;
switch (((User) contact).getStatus()) {
case ONLINE:
textColor = Color.LIMEGREEN;
break;
case AWAY:
textColor = Color.ORANGERED;
break;
case BUSY:
textColor = Color.RED;
break;
case OFFLINE:
textColor = Color.GRAY;
break;
}
infoLabel.setTextFill(textColor);
} else
// group specific infos
infoLabel = new Label(String.valueOf(((Group) contact).getContacts().size()) + " members");
setGraphic(new VBox(new Label(contact.getName()), infoLabel));
}
}
}

View File

@ -1,198 +0,0 @@
package envoy.client.ui;
import java.awt.Component;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.HashMap;
import java.util.Map;
import javax.swing.*;
/**
* This class defines a menu that will be automatically called if
* {@link MouseEvent#isPopupTrigger()} returns true for the parent component.
* The user has the possibility to directly add actions to be performed when
* clicking on the element with the selected String. Additionally, for each
* element an {@link Icon} can be added, but it must not be.
* If the key(text) of an element starts with one of the predefined values, a
* special component will be called: either a {@link JRadioButtonMenuItem}, a
* {@link JCheckBoxMenuItem} or a {@link JMenu} will be created.<br>
* <br>
* Project: <strong>envoy-client</strong><br>
* File: <strong>ContextMenu.java</strong><br>
* Created: <strong>17 Mar 2020</strong><br>
*
* @author Leon Hofmeister
* @since Envoy Client v0.1-beta
*/
public class ContextMenu extends JPopupMenu {
private static final long serialVersionUID = 0L;
/**
* If a key starts with this String, a {@link JCheckBoxMenuItem} will be created
*/
public static final String checkboxMenuItem = "ChBoMI";
/**
* If a key starts with this String, a {@link JRadioButtonMenuItem} will be
* created
*/
public static final String radioButtonMenuItem = "RaBuMI";
/**
* If a key starts with this String, a {@link JMenu} will be created
*/
public static final String subMenuItem = "SubMI";
private Map<String, ActionListener> items = new HashMap<>();
private Map<String, Icon> icons = new HashMap<>();
private Map<String, Integer> mnemonics = new HashMap<>();
private ButtonGroup radioButtonGroup = new ButtonGroup();
private boolean built = false;
/**
* @param parent the component which will call this
* {@link ContextMenu}
* @since Envoy Client v0.1-beta
*/
public ContextMenu(Component parent) { setInvoker(parent); }
/**
* @param label the string that a UI may use to display as a title
* for the pop-up menu
* @param parent the component which will call this
* {@link ContextMenu}
* @param itemsWithActions a map of all strings to be displayed with according
* actions
* @param itemIcons the icons to be displayed before a name, if wanted.
* Only keys in here will have an Icon displayed. More
* precisely, all keys here not included in the first
* map will be thrown out.
* @param itemMnemonics the keyboard shortcuts that need to be pressed to
* automatically execute the {@link JMenuItem} with the
* given text
* @since Envoy Client v0.1-beta
*/
public ContextMenu(String label, Component parent, Map<String, ActionListener> itemsWithActions, Map<String, Icon> itemIcons,
Map<String, Integer> itemMnemonics) {
super(label);
setInvoker(parent);
this.items = (itemsWithActions != null) ? itemsWithActions : items;
this.icons = (itemIcons != null) ? itemIcons : icons;
this.mnemonics = (itemMnemonics != null) ? itemMnemonics : mnemonics;
}
/**
* Prepares the PopupMenu to be displayed. Should only be used once all map
* values have been set.
*
* @return this instance of {@link ContextMenu} to allow chaining behind the
* constructor
* @since Envoy Client v0.1-beta
*/
public ContextMenu build() {
items.forEach((text, action) -> {
// case radio button wanted
AbstractButton item;
if (text.startsWith(radioButtonMenuItem)) {
item = new JRadioButtonMenuItem(text.substring(radioButtonMenuItem.length()), icons.containsKey(text) ? icons.get(text) : null);
radioButtonGroup.add(item);
// case check box wanted
} else if (text.startsWith(checkboxMenuItem))
item = new JCheckBoxMenuItem(text.substring(checkboxMenuItem.length()), icons.containsKey(text) ? icons.get(text) : null);
// case sub-menu wanted
else if (text.startsWith(subMenuItem)) item = new JMenu(text.substring(subMenuItem.length()));
else // normal JMenuItem wanted
item = new JMenuItem(text, icons.containsKey(text) ? icons.get(text) : null);
item.addActionListener(action);
if (mnemonics.containsKey(text)) item.setMnemonic(mnemonics.get(text));
add(item);
});
if (getInvoker() != null) {
getInvoker().addMouseListener(getShowingListener());
built = true;
}
return this;
}
/**
* @param label the string that a UI may use to display as a title for the
* pop-up menu.
* @since Envoy Client v0.1-beta
*/
public ContextMenu(String label) { super(label); }
private MouseAdapter getShowingListener() {
return new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) { action(e); }
@Override
public void mousePressed(MouseEvent e) { action(e); }
@Override
public void mouseReleased(MouseEvent e) { action(e); }
private void action(MouseEvent e) {
if (!built) build();
if (e.isPopupTrigger()) // hides the menu if already visible
if (!isVisible()) show(e.getComponent(), e.getX(), e.getY());
else setVisible(false);
}
};
}
/**
* Removes all subcomponents of this menu.
*
* @since Envoy Client v0.1-beta
*/
public void clear() {
removeAll();
items = new HashMap<>();
icons = new HashMap<>();
mnemonics = new HashMap<>();
}
/**
* @return the items
* @since Envoy Client v0.1-beta
*/
public Map<String, ActionListener> getItems() { return items; }
/**
* @param items the items with the displayed text and the according action to
* take once called
* @since Envoy Client v0.1-beta
*/
public void setItems(Map<String, ActionListener> items) { this.items = items; }
/**
* @return the icons
* @since Envoy Client v0.1-beta
*/
public Map<String, Icon> getIcons() { return icons; }
/**
* @param icons the icons to set
* @since Envoy Client v0.1-beta
*/
public void setIcons(Map<String, Icon> icons) { this.icons = icons; }
/**
* @return the mnemonics (the keyboard shortcuts that automatically execute the
* command for a {@link JMenuItem} with corresponding text)
* @since Envoy Client v0.1-beta
*/
public Map<String, Integer> getMnemonics() { return mnemonics; }
/**
* @param mnemonics the keyboard shortcuts that need to be pressed to
* automatically execute the {@link JMenuItem} with the given
* text
* @since Envoy Client v0.1-beta
*/
public void setMnemonics(Map<String, Integer> mnemonics) { this.mnemonics = mnemonics; }
}

View File

@ -1,12 +1,10 @@
package envoy.client.ui;
import java.awt.Image;
import java.io.IOException;
import java.util.EnumMap;
import java.util.EnumSet;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javafx.scene.image.Image;
/**
* Provides static utility methods for loading icons from the resource
@ -24,7 +22,17 @@ public class IconUtil {
private IconUtil() {}
/**
* Loads an icon from resource folder and scales it to a given size.
* Loads an icon from the resource folder.
*
* @param path the path to the icon inside the resource folder
* @return the icon
* @throws IOException if the loading process failed
* @since Envoy Client v0.1-beta
*/
public static Image load(String path) throws IOException { return new Image(IconUtil.class.getResource(path).toExternalForm()); }
/**
* Loads an icon from the resource folder and scales it to a given size.
*
* @param path the path to the icon inside the resource folder
* @param size the size to scale the icon to
@ -32,8 +40,8 @@ public class IconUtil {
* @throws IOException if the loading process failed
* @since Envoy Client v0.1-beta
*/
public static ImageIcon load(String path, int size) throws IOException {
return new ImageIcon(ImageIO.read(IconUtil.class.getResourceAsStream(path)).getScaledInstance(size, size, Image.SCALE_SMOOTH));
public static Image load(String path, int size) throws IOException {
return new Image(IconUtil.class.getResource(path).toExternalForm(), size, size, true, true);
}
/**
@ -51,8 +59,8 @@ public class IconUtil {
* @throws IOException if the loading process failed
* @since Envoy Client v0.1-beta
*/
public static <T extends Enum<T>> EnumMap<T, ImageIcon> loadByEnum(Class<T> enumClass, int size) throws IOException {
var icons = new EnumMap<T, ImageIcon>(enumClass);
public static <T extends Enum<T>> EnumMap<T, Image> loadByEnum(Class<T> enumClass, int size) throws IOException {
var icons = new EnumMap<T, Image>(enumClass);
var path = "/icons/" + enumClass.getSimpleName().toLowerCase() + "/";
for (var e : EnumSet.allOf(enumClass))
icons.put(e, load(path + e.toString().toLowerCase() + ".png", size));

View File

@ -0,0 +1,54 @@
package envoy.client.ui;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Map;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import envoy.data.Message;
import envoy.data.Message.MessageStatus;
/**
* Displays a single message inside the message list.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>MessageListCell.java</strong><br>
* Created: <strong>28.03.2020</strong><br>
*
* @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta
*/
public class MessageListCell extends ListCell<Message> {
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("dd.MM.yyyy HH:mm");
private static Map<MessageStatus, Image> statusImages;
static {
try {
statusImages = IconUtil.loadByEnum(MessageStatus.class, 32);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Displays the text, the data of creation and the status of a message.
*
* @since Envoy v0.1-beta
*/
@Override
protected void updateItem(Message message, boolean empty) {
super.updateItem(message, empty);
setGraphic(!empty && message != null ? new HBox(
new VBox(
new Label(dateFormat.format(message.getCreationDate())),
new Label(message.getText())),
new Label("", new ImageView(statusImages.get(message.getStatus())))) : null);
}
}

View File

@ -0,0 +1,152 @@
package envoy.client.ui;
import java.io.IOException;
import java.util.Stack;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import envoy.client.data.Settings;
import envoy.client.event.ThemeChangeEvent;
import envoy.event.EventBus;
/**
* Manages a stack of scenes. The most recently added scene is displayed inside
* a stage. When a scene is removed from the stack, its predecessor is
* displayed.
* <p>
* When a scene is loaded, the style sheet for the current theme is applied to
* it.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>SceneContext.java</strong><br>
* Created: <strong>06.06.2020</strong><br>
*
* @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta
*/
public final class SceneContext {
/**
* Contains information about different scenes and their FXML resource files.
*
* @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta
*/
public static enum SceneInfo {
/**
* The main scene in which chats are displayed.
*
* @since Envoy Client v0.1-beta
*/
CHAT_SCENE("/fxml/ChatScene.fxml"),
/**
* The scene in which settings are displayed.
*
* @since Envoy Client v0.1-beta
*/
SETTINGS_SCENE("/fxml/SettingsScene.fxml"),
/**
* The scene in which the contact search is displayed.
*
* @since Envoy Client v0.1-beta
*/
CONTACT_SEARCH_SCENE("/fxml/ContactSearchScene.fxml"),
/**
* The scene in which the login screen is displayed.
*
* @since Envoy Client v0.1-beta
*/
LOGIN_SCENE("/fxml/LoginScene.fxml");
/**
* The path to the FXML resource.
*/
public final String path;
SceneInfo(String path) { this.path = path; }
}
private final Stage stage;
private final FXMLLoader loader = new FXMLLoader();
private final Stack<Scene> sceneStack = new Stack<>();
private static final Settings settings = Settings.getInstance();
/**
* Initializes the scene context.
*
* @param stage the stage in which scenes will be displayed
* @since Envoy Client v0.1-beta
*/
public SceneContext(Stage stage) {
this.stage = stage;
EventBus.getInstance().register(ThemeChangeEvent.class, theme -> applyCSS());
}
/**
* Loads a new scene specified by a scene info.
*
* @param sceneInfo specifies the scene to load
* @throws RuntimeException if the loading process fails
* @since Envoy Client v0.1-beta
*/
public void load(SceneInfo sceneInfo) {
loader.setRoot(null);
loader.setController(null);
try {
final var rootNode = (Parent) loader.load(getClass().getResourceAsStream(sceneInfo.path));
final var scene = new Scene(rootNode);
sceneStack.push(scene);
stage.setScene(scene);
applyCSS();
stage.show();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* Removes the current scene and displays the previous one.
*
* @since Envoy Client v0.1-beta
*/
public void pop() {
sceneStack.pop();
if (!sceneStack.isEmpty()) {
stage.setScene(sceneStack.peek());
applyCSS();
}
stage.show();
}
private void applyCSS() {
if (!sceneStack.isEmpty()) {
final var styleSheets = stage.getScene().getStylesheets();
final var themeCSS = "/css/" + settings.getCurrentTheme() + ".css";
styleSheets.clear();
styleSheets.addAll(getClass().getResource("/css/base.css").toExternalForm(), getClass().getResource(themeCSS).toExternalForm());
}
}
/**
* @param <T> the type of the controller
* @return the controller used by the current scene
* @since Envoy Client v0.1-beta
*/
public <T> T getController() { return loader.getController(); }
/**
* @return the stage in which the scenes are displayed
* @since Envoy Client v0.1-beta
*/
public Stage getStage() { return stage; }
}

View File

@ -0,0 +1,127 @@
package envoy.client.ui;
import java.io.File;
import java.io.IOException;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.application.Application;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.stage.Stage;
import envoy.client.data.*;
import envoy.client.net.Client;
import envoy.client.ui.SceneContext.SceneInfo;
import envoy.client.ui.controller.LoginScene;
import envoy.data.Message;
import envoy.exception.EnvoyException;
import envoy.util.EnvoyLog;
/**
* Handles application startup and shutdown.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>Startup.java</strong><br>
* Created: <strong>26.03.2020</strong><br>
*
* @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta
*/
public final class Startup extends Application {
private LocalDB localDB;
private Client client;
private Cache<Message> cache;
private static final ClientConfig config = ClientConfig.getInstance();
private static final Logger logger = EnvoyLog.getLogger(Startup.class);
/**
* Loads the configuration, initializes the client and the local database and
* delegates the rest of the startup process to {@link LoginScene}.
*
* @since Envoy Client v0.1-beta
*/
@Override
public void start(Stage stage) throws Exception {
try {
// Load the configuration from client.properties first
final Properties properties = new Properties();
properties.load(Startup.class.getClassLoader().getResourceAsStream("client.properties"));
config.load(properties);
// Override configuration values with command line arguments
final String[] args = getParameters().getRaw().toArray(new String[0]);
if (args.length > 0) config.load(args);
// Check if all mandatory configuration values have been initialized
if (!config.isInitialized()) throw new EnvoyException("Configuration is not fully initialized");
} catch (final Exception e) {
new Alert(AlertType.ERROR, "Error loading configuration values:\n" + e);
e.printStackTrace();
System.exit(1);
}
// Setup logger for the envoy package
EnvoyLog.initialize(config);
EnvoyLog.attach("envoy");
EnvoyLog.setFileLevelBarrier(config.getFileLevelBarrier());
EnvoyLog.setConsoleLevelBarrier(config.getConsoleLevelBarrier());
// Initialize the local database
if (config.isIgnoreLocalDB()) {
localDB = new TransientLocalDB();
new Alert(AlertType.WARNING, "Ignoring local database.\nMessages will not be saved!").showAndWait();
} else {
try {
localDB = new PersistentLocalDB(new File(config.getHomeDirectory(), config.getLocalDB().getPath()));
} catch (final IOException e3) {
logger.log(Level.SEVERE, "Could not initialize local database", e3);
new Alert(AlertType.ERROR, "Could not initialize local database!\n" + e3).showAndWait();
System.exit(1);
return;
}
}
// Initialize client and unread message cache
client = new Client();
cache = new Cache<>();
stage.setTitle("Envoy");
stage.getIcons().add(IconUtil.load("/icons/envoy_logo.png"));
final var sceneContext = new SceneContext(stage);
sceneContext.load(SceneInfo.LOGIN_SCENE);
sceneContext.<LoginScene>getController().initializeData(client, localDB, cache, sceneContext);
}
/**
* Closes the client connection and saves the local database and settings.
*
* @since Envoy Client v0.1-beta
*/
@Override
public void stop() {
try {
logger.info("Closing connection...");
client.close();
logger.info("Saving local database and settings...");
localDB.save();
Settings.getInstance().save();
} catch (final Exception e) {
logger.log(Level.SEVERE, "Unable to save local files", e);
}
}
/**
* Starts the application.
*
* @param args the command line arguments are processed by the
* {@link ClientConfig}
* @since Envoy Client v0.1-beta
*/
public static void main(String[] args) { launch(args); }
}

View File

@ -1,146 +0,0 @@
package envoy.client.ui;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
/**
* Project: <strong>envoy-client</strong><br>
* File: <strong>Theme.java</strong><br>
* Created: <strong>23 Nov 2019</strong><br>
*
* @author Maximilian K&auml;fer
* @since Envoy Client v0.2-alpha
*/
public class Theme implements Serializable {
private static final long serialVersionUID = 0L;
private String themeName;
private Map<String, Color> colors = new HashMap<>();
/**
* Initializes a {@link Theme} with all colors relevant to the application GUI.
*
* @param themeName the name of the {@link Theme}
* @param backgroundColor the background color
* @param cellColor the cell color
* @param interactableForegroundColor the color of interactable foreground UI
* elements
* @param interactableBackgroundColor the color of interactable background UI
* elements
* @param textColor the color normal text should be displayed
* in
* @param dateColorChat the color of chat message metadata
* @param selectionColor the section color
* @param typingMessageColor the color of currently typed messages
* @param userNameColor the color of user names
* @since Envoy Client v0.2-alpha
*/
public Theme(String themeName, Color backgroundColor, Color cellColor, Color interactableForegroundColor, Color interactableBackgroundColor,
Color textColor, Color dateColorChat, Color selectionColor, Color typingMessageColor, Color userNameColor) {
this.themeName = themeName;
colors.put("backgroundColor", backgroundColor);
colors.put("cellColor", cellColor);
colors.put("interactableForegroundColor", interactableForegroundColor);
colors.put("interactableBackgroundColor", interactableBackgroundColor);
colors.put("textColor", textColor);
colors.put("dateColorChat", dateColorChat);
colors.put("selectionColor", selectionColor);
colors.put("typingMessageColor", typingMessageColor);
colors.put("userNameColor", userNameColor);
}
/**
* Initializes a {@link Theme} by copying all parameters except for the name
* from another {@link Theme} instance.
*
* @param name the name of the {@link Theme}
* @param other the {@link Theme} to copy
* @since Envoy Client v0.2-alpha
*/
public Theme(String name, Theme other) {
themeName = name;
colors.putAll(other.colors);
}
/**
* @return a {@code Map<String, Color>} of all colors defined for this theme
* with their names as keys
* @since Envoy Client v0.2-alpha
*/
public Map<String, Color> getColors() { return colors; }
/**
* @return name of the theme
* @since Envoy Client v0.2-alpha
*/
public String getThemeName() { return themeName; }
/**
* @return interactableForegroundColor
* @since Envoy Client v0.2-alpha
*/
public Color getInteractableForegroundColor() { return colors.get("interactableForegroundColor"); }
/**
* @return the {@link Color} in which the text content of a message should be
* displayed
* @since Envoy Client v0.2-alpha
*/
public Color getTextColor() { return colors.get("textColor"); }
/**
* @return the {@link Color} in which the creation date of a message should be
* displayed
* @since Envoy Client v0.2-alpha
*/
public Color getDateColor() { return colors.get("dateColorChat"); }
/**
* @return selectionColor
* @since Envoy Client v0.2-alpha
*/
public Color getSelectionColor() { return colors.get("selectionColor"); }
/**
* @return typingMessageColor
* @since Envoy Client v0.2-alpha
*/
public Color getTypingMessageColor() { return colors.get("typingMessageColor"); }
/**
* @return backgroundColor
* @since Envoy Client v0.2-alpha
*/
public Color getBackgroundColor() { return colors.get("backgroundColor"); }
/**
* @return cellColor
* @since Envoy Client v0.2-alpha
*/
public Color getCellColor() { return colors.get("cellColor"); }
/**
* @return interactableBackgroundColor
* @since Envoy Client v0.2-alpha
*/
public Color getInteractableBackgroundColor() { return colors.get("interactableBackgroundColor"); }
/**
* @return userNameColor
* @since Envoy Client v0.2-alpha
*/
public Color getUserNameColor() { return colors.get("userNameColor"); }
/**
* Sets the a specific {@link Color} in this theme to a new {@link Color}
*
* @param colorName the name of the {@link Color} to set
* @param newColor the new {@link Color} to be set
* @since Envoy 0.2-alpha
*/
public void setColor(String colorName, Color newColor) { colors.put(colorName, newColor); }
}

View File

@ -1,687 +0,0 @@
package envoy.client.ui.container;
import java.awt.*;
import java.awt.datatransfer.StringSelection;
import java.awt.event.*;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import envoy.client.data.Chat;
import envoy.client.data.LocalDB;
import envoy.client.data.Settings;
import envoy.client.event.MessageCreationEvent;
import envoy.client.event.ThemeChangeEvent;
import envoy.client.net.Client;
import envoy.client.net.WriteProxy;
import envoy.client.ui.Theme;
import envoy.client.ui.list.ComponentList;
import envoy.client.ui.list.ComponentList.SelectionMode;
import envoy.client.ui.list.Model;
import envoy.client.ui.list_component.ContactSearchComponent;
import envoy.client.ui.list_component.MessageComponent;
import envoy.client.ui.primary.PrimaryButton;
import envoy.client.ui.primary.PrimaryScrollPane;
import envoy.client.ui.primary.PrimaryTextArea;
import envoy.client.ui.renderer.UserListRenderer;
import envoy.client.ui.settings.SettingsScreen;
import envoy.data.Message;
import envoy.data.Message.MessageStatus;
import envoy.data.MessageBuilder;
import envoy.data.User;
import envoy.event.*;
import envoy.util.EnvoyLog;
/**
* Project: <strong>envoy-client</strong><br>
* File: <strong>ChatWindow.java</strong><br>
* Created: <strong>28 Sep 2019</strong><br>
*
* @author Kai S. K. Engelbart
* @author Maximilian K&auml;fer
* @author Leon Hofmeister
* @since Envoy Client v0.1-alpha
*/
public class ChatWindow extends JFrame {
/**
* This integer defines the maximum amount of chars allowed per message.
*
* @since Envoy 0.1-beta
*/
public static final int MAX_MESSAGE_LENGTH = 200;
// User specific objects
private Client client;
private WriteProxy writeProxy;
private LocalDB localDB;
private Chat currentChat;
// GUI components
private JPanel contentPane = new JPanel();
private PrimaryTextArea messageEnterTextArea = new PrimaryTextArea(space);
private JList<User> userList = new JList<>();
private DefaultListModel<User> userListModel = new DefaultListModel<>();
private ComponentList<Message> messageList = new ComponentList<>();
private PrimaryScrollPane scrollPane = new PrimaryScrollPane();
private JTextPane textPane = new JTextPane();
private PrimaryButton postButton = new PrimaryButton("Post");
private PrimaryButton settingsButton = new PrimaryButton("Settings");
private JPopupMenu contextMenu;
// Contacts Header
private JPanel contactsHeader = new JPanel();
private JTextPane contactsDisplay = new JTextPane();
private PrimaryButton addContact = new PrimaryButton("+");
// Search Contacts
private final JPanel searchPane = new JPanel();
private final PrimaryButton cancelButton = new PrimaryButton("x");
private final PrimaryTextArea searchField = new PrimaryTextArea(space);
private final PrimaryScrollPane scrollForPossibleContacts = new PrimaryScrollPane();
private final Model<User> contactsModel = new Model<>();
private final ComponentList<User> contactList = new ComponentList<User>().setRenderer(ContactSearchComponent::new);
private static final Logger logger = EnvoyLog.getLogger(ChatWindow.class);
// GUI component spacing
private final static int space = 4;
private static final Insets insets = new Insets(space, space, space, space);
private static final long serialVersionUID = 0L;
/**
* Initializes a {@link JFrame} with UI elements used to send and read messages
* to different users.
*
* @since Envoy Client v0.1-alpha
*/
public ChatWindow() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(100, 100, 600, 800);
setMinimumSize(new Dimension(400, 300));
setTitle("Envoy");
setLocationRelativeTo(null);
setIconImage(Toolkit.getDefaultToolkit().createImage(getClass().getClassLoader().getResource("envoy_logo.png")));
contentPane.setBorder(new EmptyBorder(space, space, space, space));
setContentPane(contentPane);
GridBagLayout gbl_contentPane = new GridBagLayout();
gbl_contentPane.columnWidths = new int[] { 1, 1, 1 };
gbl_contentPane.rowHeights = new int[] { 1, 1, 1, 1 };
gbl_contentPane.columnWeights = new double[] { 0.03, 1.0, 0.1 };
gbl_contentPane.rowWeights = new double[] { 0.03, 0.001, 1.0, 0.001 };
contentPane.setLayout(gbl_contentPane);
messageList.setBorder(new EmptyBorder(space, space, space, space));
messageList.setSelectionMode(SelectionMode.SINGLE);
messageList.setSelectionHandler((message, comp, isSelected) -> {
final var theme = Settings.getInstance().getCurrentTheme();
comp.setBackground(isSelected ? theme.getSelectionColor() : theme.getCellColor());
// ContextMenu
Map<String, ActionListener> commands = Map.of("forward selected message", evt -> {
final Message selectedMessage = messageList.getSingleSelectedElement();
List<User> chosenContacts = ContactsChooserDialog
.showForwardingDialog("Forward selected message to", null, selectedMessage, localDB.getUsers().values());
if (chosenContacts != null && chosenContacts.size() > 0) forwardMessage(selectedMessage, chosenContacts.toArray(new User[0]));
}, "copy", evt -> {
// TODO should be enhanced to allow also copying of message attachments,
// especially pictures
StringSelection copy = new StringSelection(messageList.getSingleSelectedElement().getText());
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(copy, copy);
// TODO insert implementation to edit and delete messages
}, "delete", evt -> {}, "edit", evt -> {}, "quote", evt -> {});
if (isSelected) {
contextMenu = new ContextMenu(null, comp, commands, null, null).build();
contextMenu.show(comp, 0, 0);
}
});
scrollPane.setViewportView(messageList);
scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
scrollPane.addComponentListener(new ComponentAdapter() {
// Update list elements when scroll pane (and thus list) is resized
@Override
public void componentResized(ComponentEvent e) {
messageList.setMaximumSize(new Dimension(scrollPane.getWidth(), Integer.MAX_VALUE));
messageList.synchronizeModel();
}
});
GridBagConstraints gbc_scrollPane = new GridBagConstraints();
gbc_scrollPane.fill = GridBagConstraints.BOTH;
gbc_scrollPane.gridwidth = 2;
gbc_scrollPane.gridheight = 2;
gbc_scrollPane.gridx = 1;
gbc_scrollPane.gridy = 1;
gbc_scrollPane.insets = insets;
drawChatBox(gbc_scrollPane);
// MessageEnterTextArea
messageEnterTextArea.addInputMethodListener(new InputMethodListener() {
@Override
public void inputMethodTextChanged(InputMethodEvent event) {
checkMessageTextLength();
checkPostButton(messageEnterTextArea.getText());
}
@Override
public void caretPositionChanged(InputMethodEvent event) {}
});
messageEnterTextArea.addKeyListener(new KeyAdapter() {
@Override
public void keyReleased(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER
&& (Settings.getInstance().isEnterToSend() && e.getModifiersEx() == 0 || e.getModifiersEx() == KeyEvent.CTRL_DOWN_MASK)
&& postButton.isEnabled())
postMessage();
// Checking if text is too long
checkMessageTextLength();
checkPostButton(messageEnterTextArea.getText());
}
});
GridBagConstraints gbc_messageEnterTextArea = new GridBagConstraints();
gbc_messageEnterTextArea.fill = GridBagConstraints.BOTH;
gbc_messageEnterTextArea.gridx = 1;
gbc_messageEnterTextArea.gridy = 3;
gbc_messageEnterTextArea.insets = insets;
contentPane.add(messageEnterTextArea, gbc_messageEnterTextArea);
// Post Button
GridBagConstraints gbc_postButton = new GridBagConstraints();
gbc_postButton.fill = GridBagConstraints.BOTH;
gbc_postButton.gridx = 2;
gbc_postButton.gridy = 3;
gbc_postButton.insets = insets;
postButton.addActionListener((evt) -> { postMessage(); });
postButton.setEnabled(false);
contentPane.add(postButton, gbc_postButton);
// Settings Button
GridBagConstraints gbc_moveSelectionSettingsButton = new GridBagConstraints();
gbc_moveSelectionSettingsButton.fill = GridBagConstraints.BOTH;
gbc_moveSelectionSettingsButton.gridx = 2;
gbc_moveSelectionSettingsButton.gridy = 0;
gbc_moveSelectionSettingsButton.insets = insets;
settingsButton.addActionListener(evt -> new SettingsScreen().setVisible(true));
contentPane.add(settingsButton, gbc_moveSelectionSettingsButton);
// Partner name display
textPane.setFont(new Font("Arial", Font.PLAIN, 20));
textPane.setEditable(false);
GridBagConstraints gbc_partnerName = new GridBagConstraints();
gbc_partnerName.fill = GridBagConstraints.HORIZONTAL;
gbc_partnerName.gridx = 1;
gbc_partnerName.gridy = 0;
gbc_partnerName.insets = insets;
contentPane.add(textPane, gbc_partnerName);
userList.setCellRenderer(new UserListRenderer());
userList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
userList.addListSelectionListener((listSelectionEvent) -> {
if (client != null && localDB != null && !listSelectionEvent.getValueIsAdjusting()) {
final JList<User> selectedUserList = (JList<User>) listSelectionEvent.getSource();
final User user = selectedUserList.getSelectedValue();
for (int i = 0; i < contentPane.getComponents().length; i++)
if (contentPane.getComponent(i).equals(searchPane)) drawChatBox(gbc_scrollPane);
if (user != null) {
// Select current chat
currentChat = localDB.getChats().stream().filter(chat -> chat.getRecipient().getID() == user.getID()).findFirst().get();
// Read current chat
readCurrentChat();
// Set chat title
textPane.setText(currentChat.getRecipient().getName());
// Update model and scroll down
messageList.setModel(currentChat.getModel());
scrollPane.setChatOpened(true);
messageList.synchronizeModel();
revalidate();
repaint();
}
}
});
userList.setFont(new Font("Arial", Font.PLAIN, 17));
userList.setBorder(new EmptyBorder(space, space, space, space));
GridBagConstraints gbc_userList = new GridBagConstraints();
gbc_userList.fill = GridBagConstraints.VERTICAL;
gbc_userList.gridx = 0;
gbc_userList.gridy = 2;
gbc_userList.gridheight = 2;
gbc_userList.anchor = GridBagConstraints.PAGE_START;
gbc_userList.insets = insets;
contentPane.add(userList, gbc_userList);
contentPane.revalidate();
// Contacts Search
GridBagConstraints gbc_searchPane = new GridBagConstraints();
gbc_searchPane.fill = GridBagConstraints.BOTH;
gbc_searchPane.gridwidth = 2;
gbc_searchPane.gridheight = 2;
gbc_searchPane.gridx = 1;
gbc_searchPane.gridy = 1;
gbc_searchPane.insets = insets;
GridBagLayout gbl_contactsSearch = new GridBagLayout();
gbl_contactsSearch.columnWidths = new int[] { 1, 1 };
gbl_contactsSearch.rowHeights = new int[] { 1, 1 };
gbl_contactsSearch.columnWeights = new double[] { 1, 0.1 };
gbl_contactsSearch.rowWeights = new double[] { 0.001, 1 };
searchPane.setLayout(gbl_contactsSearch);
GridBagConstraints gbc_searchField = new GridBagConstraints();
gbc_searchField.fill = GridBagConstraints.BOTH;
gbc_searchField.gridx = 0;
gbc_searchField.gridy = 0;
gbc_searchField.insets = new Insets(7, 4, 4, 4);
searchPane.add(searchField, gbc_searchField);
// Sends event to server, if input has changed
searchField.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void removeUpdate(DocumentEvent evt) {
if (client.isOnline()) if (searchField.getText().isEmpty()) {
contactsModel.clear();
revalidate();
repaint();
} else try {
client.sendEvent(new ContactSearchRequest(searchField.getText()));
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void insertUpdate(DocumentEvent evt) {
if (client.isOnline()) try {
client.sendEvent(new ContactSearchRequest(searchField.getText()));
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void changedUpdate(DocumentEvent evt) {}
});
GridBagConstraints gbc_cancelButton = new GridBagConstraints();
gbc_cancelButton.fill = GridBagConstraints.BOTH;
gbc_cancelButton.gridx = 1;
gbc_cancelButton.gridy = 0;
gbc_cancelButton.insets = new Insets(7, 4, 4, 4);
cancelButton.addActionListener((evt) -> { drawChatBox(gbc_scrollPane); });
searchPane.add(cancelButton, gbc_cancelButton);
contactList.setModel(contactsModel);
scrollForPossibleContacts.setBorder(new EmptyBorder(space, space, space, space));
scrollForPossibleContacts.setViewportView(contactList);
GridBagConstraints gbc_possibleContacts = new GridBagConstraints();
gbc_possibleContacts.fill = GridBagConstraints.BOTH;
gbc_possibleContacts.gridwidth = 2;
gbc_possibleContacts.gridx = 0;
gbc_possibleContacts.gridy = 1;
gbc_possibleContacts.insets = insets;
searchPane.add(scrollForPossibleContacts, gbc_possibleContacts);
// Contacts Header
GridBagConstraints gbc_contactsHeader = new GridBagConstraints();
gbc_contactsHeader.fill = GridBagConstraints.BOTH;
gbc_contactsHeader.gridx = 0;
gbc_contactsHeader.gridy = 1;
gbc_contactsHeader.insets = insets;
GridBagLayout gbl_contactHeader = new GridBagLayout();
gbl_contactHeader.columnWidths = new int[] { 1, 1 };
gbl_contactHeader.rowHeights = new int[] { 1 };
gbl_contactHeader.columnWeights = new double[] { 1, 1 };
gbl_contactHeader.rowWeights = new double[] { 1 };
contactsHeader.setLayout(gbl_contactHeader);
contactsDisplay.setEditable(false);
contactsDisplay.setFont(new Font("Arial", Font.PLAIN, 12));
contactsDisplay.setText("Contacts");
GridBagConstraints gbc_contactsDisplay = new GridBagConstraints();
gbc_contactsDisplay.fill = GridBagConstraints.BOTH;
gbc_contactsDisplay.gridx = 0;
gbc_contactsDisplay.gridy = 0;
contactsHeader.add(contactsDisplay, gbc_contactsDisplay);
addContact.setFont(new Font("Arial", Font.PLAIN, 15));
GridBagConstraints gbc_addContact = new GridBagConstraints();
gbc_addContact.fill = GridBagConstraints.BOTH;
gbc_addContact.gridx = 1;
gbc_addContact.gridy = 0;
gbc_addContact.insets = insets;
addContact.addActionListener(evt -> drawContactSearch(gbc_searchPane));
contactsHeader.add(addContact, gbc_addContact);
applyTheme(Settings.getInstance().getCurrentTheme());
contentPane.add(contactsHeader, gbc_contactsHeader);
contentPane.revalidate();
// Listen to theme changes
EventBus.getInstance().register(ThemeChangeEvent.class, evt -> applyTheme(evt.get()));
// Listen to user status changes
EventBus.getInstance().register(UserStatusChangeEvent.class, evt -> { userList.revalidate(); userList.repaint(); });
// Listen to received messages
EventBus.getInstance().register(MessageCreationEvent.class, evt -> {
Message message = evt.get();
Chat chat = localDB.getChats().stream().filter(c -> c.getRecipient().getID() == message.getSenderID()).findFirst().get();
chat.appendMessage(message);
// Read message and update UI if in current chat
if (chat == currentChat) readCurrentChat();
revalidate();
repaint();
});
// Listen to message status changes
EventBus.getInstance().register(MessageStatusChangeEvent.class, evt -> {
final long id = evt.getID();
final MessageStatus status = evt.get();
for (Chat c : localDB.getChats())
for (Message m : c.getModel())
if (m.getID() == id) {
// Update message status
m.setStatus(status);
// Update model and scroll down if current chat
if (c == currentChat) {
messageList.setModel(currentChat.getModel());
scrollPane.setChatOpened(true);
} else messageList.synchronizeModel();
}
revalidate();
repaint();
});
// Listen to contact search results
EventBus.getInstance()
.register(ContactSearchResult.class,
evt -> {
contactsModel.clear();
final java.util.List<User> contacts = evt.get();
contacts.forEach(contactsModel::add);
revalidate();
repaint();
});
// Add new contacts to the contact list
EventBus.getInstance().register(ContactOperationEvent.class, evt -> {
User contact = evt.get();
// Clearing the search field and the searchResultList
searchField.setText("");
contactsModel.clear();
// Update LocalDB
userListModel.addElement(contact);
localDB.getUsers().put(contact.getName(), contact);
localDB.getChats().add(new Chat(contact));
revalidate();
repaint();
});
revalidate();
repaint();
}
/**
* Used to immediately reload the {@link ChatWindow} when settings were changed.
*
* @param theme the theme to change colors into
* @since Envoy Client v0.2-alpha
*/
private void applyTheme(Theme theme) {
// contentPane
contentPane.setBackground(theme.getBackgroundColor());
contentPane.setForeground(theme.getUserNameColor());
// messageList
messageList.setForeground(theme.getTextColor());
messageList.setBackground(theme.getCellColor());
messageList.synchronizeModel();
// scrollPane
scrollPane.applyTheme(theme);
scrollPane.autoscroll();
// messageEnterTextArea
messageEnterTextArea.setCaretColor(theme.getTypingMessageColor());
messageEnterTextArea.setForeground(theme.getTypingMessageColor());
messageEnterTextArea.setBackground(theme.getCellColor());
// postButton
postButton.setForeground(theme.getInteractableForegroundColor());
postButton.setBackground(theme.getInteractableBackgroundColor());
// settingsButton
settingsButton.setForeground(theme.getInteractableForegroundColor());
settingsButton.setBackground(theme.getInteractableBackgroundColor());
// textPane
textPane.setBackground(theme.getBackgroundColor());
textPane.setForeground(theme.getUserNameColor());
// userList
userList.setSelectionForeground(theme.getUserNameColor());
userList.setSelectionBackground(theme.getSelectionColor());
userList.setForeground(theme.getUserNameColor());
userList.setBackground(theme.getCellColor());
// contacts header
contactsHeader.setBackground(theme.getCellColor());
contactsDisplay.setBackground(theme.getCellColor());
contactsDisplay.setForeground(theme.getUserNameColor());
addContact.setBackground(theme.getInteractableBackgroundColor());
addContact.setForeground(theme.getInteractableForegroundColor());
// SearchPane
searchPane.setBackground(theme.getCellColor());
searchField.setBackground(theme.getBackgroundColor());
searchField.setForeground(theme.getUserNameColor());
cancelButton.setBackground(theme.getInteractableBackgroundColor());
cancelButton.setForeground(theme.getInteractableForegroundColor());
contactList.setForeground(theme.getTextColor());
contactList.setBackground(theme.getCellColor());
scrollForPossibleContacts.applyTheme(theme);
}
/**
* Sends a new message to the server based on the text entered in the textArea.
*
* @since Envoy Client v0.1-beta
*/
private void postMessage() {
if (userList.isSelectionEmpty()) {
JOptionPane.showMessageDialog(this, "Please select a recipient!", "Cannot send message", JOptionPane.INFORMATION_MESSAGE);
return;
}
String text = messageEnterTextArea.getText().trim();
if (!text.isEmpty()) checkMessageTextLength();
// Create message
final Message message = new MessageBuilder(localDB.getUser().getID(), currentChat.getRecipient().getID(), localDB.getIDGenerator())
.setText(text)
.build();
sendMessage(message);
// Clear text field
messageEnterTextArea.setText("");
postButton.setEnabled(false);
}
/**
* Forwards a message.
*
* @param message the message to forward
* @param recipient the new recipient of the message
* @since Envoy Client v0.1-beta
*/
private void forwardMessage(Message message, User... recipients) {
Arrays.stream(recipients).forEach(recipient -> {
if (message != null && recipients != null) sendMessage(new MessageBuilder(message, recipient.getID(), localDB.getIDGenerator()).build());
else throw new NullPointerException("No recipient or no message selected");
});
}
@SuppressWarnings("unused")
private void forwardMessages(Collection<Message> messages, User... recipients) {
messages.forEach(message -> { forwardMessage(message, recipients); });
}
/**
* Sends a {@link Message} to the server.
*
* @param message the message to send
* @since Envoy Client v0.1-beta
*/
private void sendMessage(final Message message) {
try {
// Send message
writeProxy.writeMessage(message);
// Add message to PersistentLocalDB and update UI
currentChat.appendMessage(message);
// Update UI
revalidate();
repaint();
// Request a new id generator if all IDs were used
if (!localDB.getIDGenerator().hasNext()) client.requestIdGenerator();
} catch (Exception e) {
JOptionPane.showMessageDialog(this, "Error sending message:\n" + e.toString(), "Message sending error", JOptionPane.ERROR_MESSAGE);
e.printStackTrace();
}
}
private void readCurrentChat() {
try {
currentChat.read(writeProxy);
if (messageList.getRenderer() != null) messageList.synchronizeModel();
} catch (IOException e) {
e.printStackTrace();
logger.log(Level.WARNING, "Couldn't notify server about message status change", e);
}
}
private void drawChatBox(GridBagConstraints gbc_scrollPane) {
contentPane.remove(searchPane);
contentPane.add(scrollPane, gbc_scrollPane);
contentPane.revalidate();
contentPane.repaint();
}
private void drawContactSearch(GridBagConstraints gbc_searchPane) {
currentChat = null;
userList.removeSelectionInterval(0, userList.getModel().getSize() - 1);
messageList.setModel(null);
textPane.setText("");
contentPane.remove(scrollPane);
contentPane.add(searchPane, gbc_searchPane);
contentPane.revalidate();
contentPane.repaint();
}
/**
* Initializes the components responsible server communication and
* persistence.<br>
* <br>
* This will trigger the display of the contact list.
*
* @param client the client used to send and receive messages
* @param localDB the local database used to manage stored messages
* and users
* @param writeProxy the write proxy used to send messages and status change
* events to the server or cache them inside the local
* database
* @since Envoy Client v0.3-alpha
*/
public void initContent(Client client, LocalDB localDB, WriteProxy writeProxy) {
this.client = client;
this.localDB = localDB;
this.writeProxy = writeProxy;
messageList.setRenderer((list, message) -> new MessageComponent(list, message, client.getSender().getID()));
// Load users and chats
new Thread(() -> {
localDB.getUsers().values().forEach(user -> {
userListModel.addElement(user);
// Check if user exists in local DB
if (localDB.getChats().stream().noneMatch(c -> c.getRecipient().getID() == user.getID())) localDB.getChats().add(new Chat(user));
});
SwingUtilities.invokeLater(() -> userList.setModel(userListModel));
revalidate();
repaint();
}).start();
}
/**
* Checks whether the length of the text inside messageEnterTextArea >=
* {@link ChatWindow#MAX_MESSAGE_LENGTH}
* and splits the text into the allowed part, if that is the case.
*
* @since Envoy Client v0.1-beta
*/
private void checkMessageTextLength() {
String input = messageEnterTextArea.getText();
if (input.length() >= MAX_MESSAGE_LENGTH) {
messageEnterTextArea.setText(input.substring(0, MAX_MESSAGE_LENGTH - 1));
// TODO: current notification is like being hit with a hammer, maybe it should
// be replaced with a more subtle notification
JOptionPane.showMessageDialog(messageEnterTextArea,
"the maximum length for a message has been reached",
"maximum message length reached",
JOptionPane.WARNING_MESSAGE);
}
}
private void checkPostButton(String text) { postButton.setEnabled(!text.trim().isBlank()); }
}

View File

@ -1,145 +0,0 @@
package envoy.client.ui.container;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
import envoy.client.data.Settings;
import envoy.client.ui.Theme;
import envoy.client.ui.list.ComponentList;
import envoy.client.ui.list.ComponentList.SelectionMode;
import envoy.client.ui.list.Model;
import envoy.client.ui.list_component.UserComponent;
import envoy.data.Message;
import envoy.data.User;
/**
* This class defines a dialog to choose contacts from.<br>
* <br>
* Project: <strong>envoy-client</strong><br>
* File: <strong>ContactsChooserDialog.java</strong><br>
* Created: <strong>15 Mar 2020</strong><br>
*
* @author Leon Hofmeister
* @since Envoy Client v0.1-beta
*/
public class ContactsChooserDialog extends JDialog {
private static final long serialVersionUID = 0L;
private ComponentList<User> contactList = new ComponentList<User>().setModel(new Model<User>())
.setRenderer((list, user) -> new UserComponent(user));
private JButton okButton = new JButton("Ok");
private JButton cancelButton = new JButton("Cancel");
private final Theme theme = Settings.getInstance().getCurrentTheme();
private final JPanel contentPanel = new JPanel();
/**
* Shows a modal contacts-chooser dialog and blocks until the
* dialog is hidden. If the user presses the "OK" button, then
* this method hides/disposes the dialog and returns the selected element (has
* yet
* to be casted back to its original type due to the limitations of Generics in
* Java).
* If the user presses the "Cancel" button or closes the dialog without
* pressing "OK", then this method disposes the dialog and returns an empty
* <code>ArrayList</code>.
*
* @param title the title of the dialog
* @param parent this @{@link Component} will be parsed to
* {@link java.awt.Window#setLocationRelativeTo(Component)} in
* order to change the location of the dialog
* @param message the {@link Message} to display on top of the Dialog
* @param users the users that should be displayed
* @return the selected Element (yet has to be casted to the wanted type due to
* the Generics limitations in Java)
* @since Envoy Client v0.1-beta
*/
public static List<User> showForwardingDialog(String title, Component parent, Message message, Collection<User> users) {
ContactsChooserDialog dialog = new ContactsChooserDialog(parent);
dialog.setTitle(title);
dialog.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
dialog.addCancelButtonActionListener(e -> dialog.dispose());
List<User> results = new ArrayList<>();
dialog.addOkButtonActionListener(e -> { results.addAll(dialog.getContactList().getSelectedElements()); dialog.dispose(); });
Model<User> contactListModel = dialog.getContactList().getModel();
users.forEach(contactListModel::add);
dialog.setModalityType(ModalityType.APPLICATION_MODAL);
dialog.setVisible(true);
return results;
}
/**
* @param parent this @{@link Component} will be parsed to
* {@link java.awt.Window#setLocationRelativeTo(Component)}
* @since Envoy Client v0.1-beta
*/
private ContactsChooserDialog(Component parent) {
contactList.setSelectionMode(SelectionMode.MULTIPLE);
contactList.setSelectionHandler((user, comp, isSelected) -> {
final var theme = Settings.getInstance().getCurrentTheme();
comp.setBackground(isSelected ? theme.getSelectionColor() : theme.getCellColor());
});
setLocationRelativeTo(parent);
getContentPane().setLayout(new BorderLayout());
setBackground(theme.getBackgroundColor());
setForeground(theme.getTextColor());
setSize(400, 400);
contentPanel.setBorder(new EmptyBorder(5, 5, 5, 5));
getContentPane().add(contentPanel, BorderLayout.CENTER);
contentPanel.setLayout(new BorderLayout(0, 0));
contentPanel.add(contactList, BorderLayout.CENTER);
{
JPanel buttonPane = new JPanel();
getContentPane().add(buttonPane, BorderLayout.SOUTH);
{
okButton = new JButton("OK");
okButton.setMnemonic(KeyEvent.VK_ENTER);
okButton.setActionCommand("OK");
buttonPane.setLayout(new BorderLayout(0, 0));
buttonPane.add(okButton, BorderLayout.EAST);
getRootPane().setDefaultButton(okButton);
}
{
cancelButton = new JButton("Cancel");
cancelButton.setActionCommand("Cancel");
buttonPane.add(cancelButton, BorderLayout.WEST);
}
}
applyTheme(Settings.getInstance().getCurrentTheme());
}
private void applyTheme(Theme theme) {
contentPanel.setBackground(theme.getBackgroundColor());
contentPanel.setForeground(theme.getTextColor());
contactList.setBackground(theme.getCellColor());
okButton.setBackground(theme.getInteractableBackgroundColor());
okButton.setForeground(theme.getTextColor());
cancelButton.setBackground(theme.getInteractableBackgroundColor());
cancelButton.setForeground(theme.getTextColor());
}
/**
* @return the underlying {@link ComponentList}
* @since Envoy Client v0.1-beta
*/
private ComponentList<User> getContactList() { return contactList; }
private void addOkButtonActionListener(ActionListener l) { okButton.addActionListener(l); }
private void addCancelButtonActionListener(ActionListener l) { cancelButton.addActionListener(l); }
}

View File

@ -1,255 +0,0 @@
package envoy.client.ui.container;
import java.awt.Color;
import java.awt.Component;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.HashMap;
import java.util.Map;
import javax.swing.*;
import envoy.client.data.Settings;
import envoy.client.ui.Theme;
/**
* This class defines a menu that will be automatically called if
* {@link MouseEvent#isPopupTrigger()} returns true for the parent component.
* The user has the possibility to directly add actions to be performed when
* clicking on the element with the selected String. Additionally, for each
* element an {@link Icon} can be added, but it must not be.
* If the key(text) of an element starts with one of the predefined values, a
* special component will be called: either a {@link JRadioButtonMenuItem}, a
* {@link JCheckBoxMenuItem} or a {@link JMenu} will be created.<br>
* <br>
* Project: <strong>envoy-client</strong><br>
* File: <strong>ContextMenu.java</strong><br>
* Created: <strong>17 Mar 2020</strong><br>
*
* @author Leon Hofmeister
* @since Envoy Client v0.1-beta
*/
public class ContextMenu extends JPopupMenu {
private static final long serialVersionUID = 0L;
/**
* If a key starts with this String, a {@link JCheckBoxMenuItem} will be created
*/
public static final String checkboxMenuItem = "ChBoMI";
/**
* If a key starts with this String, a {@link JRadioButtonMenuItem} will be
* created
*/
public static final String radioButtonMenuItem = "RaBuMI";
/**
* If a key starts with this String, a {@link JMenu} will be created
*/
public static final String subMenuItem = "SubMI";
private Map<String, ActionListener> items = new HashMap<>();
private Map<String, Icon> icons = new HashMap<>();
private Map<String, Integer> mnemonics = new HashMap<>();
private ButtonGroup radioButtonGroup = new ButtonGroup();
private boolean built = false;
private boolean visible = false;
/**
* @param parent the component which will call this
* {@link ContextMenu}
* @since Envoy Client v0.1-beta
*/
public ContextMenu(Component parent) {
setInvoker(parent);
setOpaque(true);
}
/**
* @param label the string that a UI may use to display as a title
* for the pop-up menu
* @param parent the component which will call this
* {@link ContextMenu}
* @param itemsWithActions a map of all strings to be displayed with according
* actions
* @param itemIcons the icons to be displayed before a name, if wanted.
* Only keys in here will have an Icon displayed. More
* precisely, all keys here not included in the first
* map will be thrown out.
* @param itemMnemonics the keyboard shortcuts that need to be pressed to
* automatically execute the {@link JMenuItem} with the
* given text
* @since Envoy Client v0.1-beta
*/
public ContextMenu(String label, Component parent, Map<String, ActionListener> itemsWithActions, Map<String, Icon> itemIcons,
Map<String, Integer> itemMnemonics) {
this(label);
setInvoker(parent);
this.items = (itemsWithActions != null) ? itemsWithActions : items;
this.icons = (itemIcons != null) ? itemIcons : icons;
this.mnemonics = (itemMnemonics != null) ? itemMnemonics : mnemonics;
}
/**
* @param label the string that a UI may use to display as a title for the
* pop-up menu.
* @since Envoy Client v0.1-beta
*/
public ContextMenu(String label) {
super(label);
setOpaque(true);
}
/**
* Prepares the PopupMenu to be displayed. Should only be used once all map
* values have been set.
*
* @return this instance of {@link ContextMenu} to allow chaining behind the
* constructor
* @since Envoy Client v0.1-beta
*/
public ContextMenu build() {
items.forEach((text, action) -> {
// case radio button wanted
AbstractButton item;
if (text.startsWith(radioButtonMenuItem)) {
item = new JRadioButtonMenuItem(text.substring(radioButtonMenuItem.length()), icons.containsKey(text) ? icons.get(text) : null);
radioButtonGroup.add(item);
// case check box wanted
} else if (text.startsWith(checkboxMenuItem))
item = new JCheckBoxMenuItem(text.substring(checkboxMenuItem.length()), icons.containsKey(text) ? icons.get(text) : null);
// case sub-menu wanted
else if (text.startsWith(subMenuItem)) item = new JMenu(text.substring(subMenuItem.length()));
else // normal JMenuItem wanted
item = new JMenuItem(text, icons.containsKey(text) ? icons.get(text) : null);
item.addActionListener(action);
item.setOpaque(true);
if (mnemonics.containsKey(text)) item.setMnemonic(mnemonics.get(text));
add(item);
});
getInvoker().addMouseListener(getShowingListener());
applyTheme(Settings.getInstance().getCurrentTheme());
built = true;
return this;
}
private MouseAdapter getShowingListener() {
return new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) { action(e); }
@Override
public void mousePressed(MouseEvent e) { action(e); }
@Override
public void mouseReleased(MouseEvent e) { action(e); }
private void action(MouseEvent e) {
if (!built) build();
if (e.isPopupTrigger()) {
// hides the menu if already visible
visible = !visible;
if (visible) show(e.getComponent(), e.getX(), e.getY());
else setVisible(false);
}
}
};
}
/**
* Removes all subcomponents of this menu.
*
* @since Envoy Client v0.1-beta
*/
public void clear() {
removeAll();
items = new HashMap<>();
icons = new HashMap<>();
mnemonics = new HashMap<>();
}
/**
* @return the items
* @since Envoy Client v0.1-beta
*/
public Map<String, ActionListener> getItems() { return items; }
/**
* @param items the items with the displayed text and the according action to
* take once called
* @since Envoy Client v0.1-beta
*/
public void setItems(Map<String, ActionListener> items) { this.items = items; }
/**
* @return the icons
* @since Envoy Client v0.1-beta
*/
public Map<String, Icon> getIcons() { return icons; }
/**
* @param icons the icons to set
* @since Envoy Client v0.1-beta
*/
public void setIcons(Map<String, Icon> icons) { this.icons = icons; }
/**
* @return the mnemonics (the keyboard shortcuts that automatically execute the
* command for a {@link JMenuItem} with corresponding text)
* @since Envoy Client v0.1-beta
*/
public Map<String, Integer> getMnemonics() { return mnemonics; }
/**
* @param mnemonics the keyboard shortcuts that need to be pressed to
* automatically execute the {@link JMenuItem} with the given
* text
* @since Envoy Client v0.1-beta
*/
public void setMnemonics(Map<String, Integer> mnemonics) { this.mnemonics = mnemonics; }
/**
* {@inheritDoc}<br>
* Additionally sets the foreground of all subcomponents of this
* {@link ContextMenu}.
*
* @since Envoy Client v0.1-beta
*/
@Override
public void setForeground(Color color) {
super.setForeground(color);
for (MenuElement element : getSubElements())
((Component) element).setForeground(color);
}
/**
* {@inheritDoc}<br>
* Additionally sets the background of all subcomponents of this
* {@link ContextMenu}.
*
* @since Envoy Client v0.1-beta
*/
@Override
public void setBackground(Color color) {
super.setBackground(color);
for (MenuElement element : getSubElements())
((Component) element).setBackground(color);
}
/**
* Sets the fore- and background of all elements contained in this
* {@link ContextMenu}
* This method is to be only used by Envoy as {@link Theme} is an
* Envoy-exclusive object.
*
* @param theme the theme to use
* @since Envoy Client v0.1-beta
*/
protected void applyTheme(Theme theme) {
setBackground(theme.getCellColor());
setForeground(theme.getTextColor());
}
}

View File

@ -1,346 +0,0 @@
package envoy.client.ui.container;
import java.awt.*;
import java.awt.event.ItemEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.logging.Logger;
import javax.naming.TimeLimitExceededException;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import envoy.client.data.*;
import envoy.client.event.HandshakeSuccessfulEvent;
import envoy.client.net.Client;
import envoy.client.ui.Theme;
import envoy.client.ui.primary.PrimaryButton;
import envoy.data.LoginCredentials;
import envoy.data.Message;
import envoy.data.User;
import envoy.event.EventBus;
import envoy.event.HandshakeRejectionEvent;
import envoy.exception.EnvoyException;
import envoy.util.EnvoyLog;
/**
* Project: <strong>envoy-client</strong><br>
* File: <strong>LoginDialog.java</strong><br>
* Created: <strong>01.01.2020</strong><br>
*
* @author Kai S. K. Engelbart
* @author Maximilian K&auml;fer
* @since Envoy Client v0.3-alpha
*/
public class LoginDialog extends JDialog {
private JPanel contentPanel;
private JTextField textField;
private JPasswordField passwordField;
private JPasswordField repeatPasswordField;
private JLabel lblUserName;
private JLabel lblPassword;
private JLabel lblRepeatPassword;
private JLabel errorMessage;
private GridBagConstraints gbc_lblRepeatPassword;
private GridBagConstraints gbc_repeatPasswordField;
private GridBagConstraints gbc_errorMessage;
private JPanel buttonPane;
private JTextPane registerText;
private JCheckBox registerCheckBox;
private PrimaryButton okButton;
private PrimaryButton cancelButton;
private LoginCredentials credentials;
private final Client client;
private final LocalDB localDB;
private final Cache<Message> receivedMessageCache;
private static final ClientConfig config = ClientConfig.getInstance();
private static final Logger logger = EnvoyLog.getLogger(LoginDialog.class);
private static final long serialVersionUID = 0L;
/**
* Displays a dialog enabling the user to enter their user name and password.
*
* @param client the client used to perform the handshake
* @param localDB the local database in which data is persisted
* @param receivedMessageCache the cache that stored messages received during
* the handshake
* @since Envoy Client v0.3-alpha
*/
public LoginDialog(Client client, LocalDB localDB, Cache<Message> receivedMessageCache) {
this.client = client;
this.localDB = localDB;
this.receivedMessageCache = receivedMessageCache;
// Prepare handshake
localDB.loadIDGenerator();
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) { abortLogin(); }
});
initUi();
okButton.addActionListener((evt) -> {
try {
if (registerCheckBox.isSelected()) {
// Check password equality
if (Arrays.equals(passwordField.getPassword(), repeatPasswordField.getPassword())) {
credentials = new LoginCredentials(textField.getText(), passwordField.getPassword(), true);
performHandshake();
} else {
JOptionPane.showMessageDialog(this, "The repeated password is not the original password!");
clearPasswordFields();
}
} else {
credentials = new LoginCredentials(textField.getText(), passwordField.getPassword(), false);
performHandshake();
}
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
});
// Listen to handshake rejections
EventBus.getInstance()
.register(HandshakeRejectionEvent.class,
evt -> { clearPasswordFields(); errorMessage.setVisible(true); errorMessage.setText(evt.get()); });
// Exit the application when the dialog is cancelled
cancelButton.addActionListener(evt -> abortLogin());
// Log in directly if configured
if (config.hasLoginCredentials()) {
credentials = config.getLoginCredentials();
performHandshake();
return;
}
setVisible(true);
}
private void performHandshake() {
try {
client.performHandshake(credentials, receivedMessageCache);
if (client.isOnline()) {
client.initReceiver(localDB, receivedMessageCache);
dispose();
}
} catch (IOException | InterruptedException | TimeLimitExceededException e) {
logger.warning("Could not connect to server. Trying offline mode...");
e.printStackTrace();
try {
// Try entering offline mode
localDB.loadUsers();
User clientUser = localDB.getUsers().get(credentials.getIdentifier());
if (clientUser == null) throw new EnvoyException("Could not enter offline mode: user name unknown");
client.setSender(clientUser);
JOptionPane.showMessageDialog(null,
"A connection to the server could not be established. Starting in offline mode.\n" + e,
"Connection error",
JOptionPane.WARNING_MESSAGE);
dispose();
} catch (Exception e1) {
JOptionPane.showMessageDialog(null, e1, "Client error", JOptionPane.ERROR_MESSAGE);
System.exit(1);
return;
}
}
}
private void initUi() {
setSize(338, 123);
setLocationRelativeTo(null);
setResizable(false);
getContentPane().setLayout(new BorderLayout());
contentPanel = new JPanel();
contentPanel.setBorder(new EmptyBorder(5, 5, 5, 5));
getContentPane().add(contentPanel, BorderLayout.CENTER);
GridBagLayout gbl_contentPanel = new GridBagLayout();
gbl_contentPanel.columnWidths = new int[] { 0, 0, 0 };
gbl_contentPanel.rowHeights = new int[] { 0, 0, 0 };
gbl_contentPanel.columnWeights = new double[] { 0.0, 1.0, Double.MIN_VALUE };
gbl_contentPanel.rowWeights = new double[] { 0.0, 0.0, Double.MIN_VALUE };
contentPanel.setLayout(gbl_contentPanel);
lblUserName = new JLabel("Username:");
GridBagConstraints gbc_lblUserName = new GridBagConstraints();
gbc_lblUserName.anchor = GridBagConstraints.EAST;
gbc_lblUserName.insets = new Insets(0, 0, 5, 5);
gbc_lblUserName.gridx = 0;
gbc_lblUserName.gridy = 0;
contentPanel.add(lblUserName, gbc_lblUserName);
textField = new JTextField();
textField.setBorder(null);
GridBagConstraints gbc_textField = new GridBagConstraints();
gbc_textField.insets = new Insets(0, 0, 5, 0);
gbc_textField.fill = GridBagConstraints.HORIZONTAL;
gbc_textField.gridx = 1;
gbc_textField.gridy = 0;
contentPanel.add(textField, gbc_textField);
textField.setColumns(10);
lblPassword = new JLabel("Password:");
GridBagConstraints gbc_lblPassword = new GridBagConstraints();
gbc_lblPassword.anchor = GridBagConstraints.EAST;
gbc_lblPassword.insets = new Insets(0, 0, 0, 5);
gbc_lblPassword.gridx = 0;
gbc_lblPassword.gridy = 1;
contentPanel.add(lblPassword, gbc_lblPassword);
passwordField = new JPasswordField();
passwordField.setBorder(null);
GridBagConstraints gbc_passwordField = new GridBagConstraints();
gbc_passwordField.fill = GridBagConstraints.HORIZONTAL;
gbc_passwordField.gridx = 1;
gbc_passwordField.gridy = 1;
contentPanel.add(passwordField, gbc_passwordField);
lblRepeatPassword = new JLabel("Repeat Password:");
gbc_lblRepeatPassword = new GridBagConstraints();
gbc_lblRepeatPassword.anchor = GridBagConstraints.EAST;
gbc_lblRepeatPassword.insets = new Insets(0, 0, 0, 5);
gbc_lblRepeatPassword.gridx = 0;
gbc_lblRepeatPassword.gridy = 2;
repeatPasswordField = new JPasswordField();
gbc_repeatPasswordField = new GridBagConstraints();
gbc_repeatPasswordField.fill = GridBagConstraints.HORIZONTAL;
gbc_repeatPasswordField.gridx = 1;
gbc_repeatPasswordField.gridy = 2;
errorMessage = new JLabel();
gbc_errorMessage = new GridBagConstraints();
gbc_errorMessage.gridx = 1;
gbc_errorMessage.gridy = 3;
gbc_errorMessage.fill = GridBagConstraints.HORIZONTAL;
gbc_errorMessage.insets = new Insets(5, 5, 5, 5);
errorMessage.setForeground(Color.RED);
errorMessage.setVisible(false);
contentPanel.add(errorMessage, gbc_errorMessage);
buttonPane = new JPanel();
registerText = new JTextPane();
registerText.setEditable(false);
registerText.setText("Register?");
registerText.setFont(new Font("Arial", Font.BOLD, 12));
registerText.setAlignmentX(LEFT_ALIGNMENT);
buttonPane.add(registerText);
registerCheckBox = new JCheckBox();
registerCheckBox.setAlignmentX(LEFT_ALIGNMENT);
registerCheckBox.addItemListener(e -> {
switch (e.getStateChange()) {
case ItemEvent.SELECTED:
contentPanel.add(lblRepeatPassword, gbc_lblRepeatPassword);
contentPanel.add(repeatPasswordField, gbc_repeatPasswordField);
setSize(338, 173);
break;
case ItemEvent.DESELECTED:
if (repeatPasswordField.getParent() == contentPanel) {
contentPanel.remove(lblRepeatPassword);
contentPanel.remove(repeatPasswordField);
setSize(338, 148);
}
break;
}
contentPanel.revalidate();
contentPanel.repaint();
});
buttonPane.add(registerCheckBox);
buttonPane.setLayout(new FlowLayout(FlowLayout.RIGHT));
getContentPane().add(buttonPane, BorderLayout.SOUTH);
okButton = new PrimaryButton("OK");
okButton.setActionCommand("OK");
buttonPane.add(okButton);
getRootPane().setDefaultButton(okButton);
cancelButton = new PrimaryButton("Cancel");
cancelButton.setActionCommand("Cancel");
buttonPane.add(cancelButton);
setTheme();
setModalityType(Dialog.DEFAULT_MODALITY_TYPE);
EventBus.getInstance().register(HandshakeSuccessfulEvent.class, evt -> dispose());
}
/**
* Resets the text stored in the password fields.
*
* @since Envoy Client v0.3-alpha
*/
private void clearPasswordFields() {
passwordField.setText(null);
repeatPasswordField.setText(null);
}
private void setTheme() {
Theme theme = Settings.getInstance().getCurrentTheme();
// Panels
contentPanel.setBackground(theme.getBackgroundColor());
contentPanel.setForeground(theme.getBackgroundColor());
buttonPane.setBackground(theme.getBackgroundColor());
buttonPane.setForeground(theme.getBackgroundColor());
// Input Fields
textField.setBackground(theme.getCellColor());
textField.setForeground(theme.getUserNameColor());
passwordField.setBackground(theme.getCellColor());
passwordField.setForeground(theme.getUserNameColor());
repeatPasswordField.setBackground(theme.getCellColor());
repeatPasswordField.setForeground(theme.getUserNameColor());
// JLabels
lblUserName.setBackground(theme.getCellColor());
lblUserName.setForeground(theme.getUserNameColor());
lblPassword.setBackground(theme.getCellColor());
lblPassword.setForeground(theme.getUserNameColor());
lblRepeatPassword.setBackground(theme.getCellColor());
lblRepeatPassword.setForeground(theme.getUserNameColor());
// Register
registerText.setBackground(theme.getCellColor());
registerText.setForeground(theme.getUserNameColor());
registerCheckBox.setBackground(theme.getCellColor());
// Buttons
okButton.setBackground(theme.getInteractableBackgroundColor());
okButton.setForeground(theme.getInteractableForegroundColor());
cancelButton.setBackground(theme.getInteractableBackgroundColor());
cancelButton.setForeground(theme.getInteractableForegroundColor());
}
/**
* Shuts the system down properly if the login was aborted.
*
* @since Envoy Client v0.1-beta
*/
private void abortLogin() {
logger.info("The login process has been cancelled. Exiting...");
System.exit(0);
}
}

View File

@ -1,13 +0,0 @@
/**
* This package contains all graphical Containers, like Dialogs and Frames.<br>
* <br>
* Project: <strong>envoy-client</strong><br>
* File: <strong>package-info.java</strong><br>
* Created: <strong>16 Mar 2020</strong><br>
*
* @author Leon Hofmeister
* @author Kai S. K. Engelbart
* @author Maximilian K&auml;fer
* @since Envoy Client v0.1-beta
*/
package envoy.client.ui.container;

View File

@ -0,0 +1,283 @@
package envoy.client.ui.controller;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.paint.Color;
import envoy.client.data.Chat;
import envoy.client.data.LocalDB;
import envoy.client.data.Settings;
import envoy.client.event.MessageCreationEvent;
import envoy.client.net.Client;
import envoy.client.net.WriteProxy;
import envoy.client.ui.ContactListCell;
import envoy.client.ui.MessageListCell;
import envoy.client.ui.SceneContext;
import envoy.data.Contact;
import envoy.data.Message;
import envoy.data.MessageBuilder;
import envoy.event.EventBus;
import envoy.event.MessageStatusChangeEvent;
import envoy.event.UserStatusChangeEvent;
import envoy.event.contact.ContactOperationEvent;
import envoy.util.EnvoyLog;
/**
* Project: <strong>envoy-client</strong><br>
* File: <strong>ChatSceneController.java</strong><br>
* Created: <strong>26.03.2020</strong><br>
*
* @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta
*/
public final class ChatScene {
@FXML
private Label contactLabel;
@FXML
private ListView<Message> messageList;
@FXML
private ListView<Contact> userList;
@FXML
private Button postButton;
@FXML
private Button settingsButton;
@FXML
private TextArea messageTextArea;
@FXML
private Label remainingChars;
private LocalDB localDB;
private Client client;
private WriteProxy writeProxy;
private SceneContext sceneContext;
private Chat currentChat;
private static final Settings settings = Settings.getInstance();
private static final EventBus eventBus = EventBus.getInstance();
private static final Logger logger = EnvoyLog.getLogger(ChatScene.class);
private static final int MAX_MESSAGE_LENGTH = 255;
/**
* Initializes the appearance of certain visual components.
*
* @since Envoy Client v0.1-beta
*/
@FXML
private void initialize() {
// Initialize message and user rendering
messageList.setCellFactory(listView -> new MessageListCell());
userList.setCellFactory(listView -> new ContactListCell());
// Listen to received messages
eventBus.register(MessageCreationEvent.class, e -> {
final var message = e.get();
final var chat = localDB.getChats().stream().filter(c -> c.getRecipient().getID() == message.getSenderID()).findAny().get();
// Update UI if in current chat
if (chat == currentChat) Platform.runLater(() -> messageList.getItems().add(message));
});
// Listen to message status changes
eventBus.register(MessageStatusChangeEvent.class, e -> {
final var message = localDB.getMessage(e.getID());
message.setStatus(e.get());
// Update UI if in current chat
if (currentChat != null && message.getSenderID() == currentChat.getRecipient().getID()) Platform.runLater(messageList::refresh);
});
// Listen to user status changes
eventBus.register(UserStatusChangeEvent.class, e -> Platform.runLater(userList::refresh));
// Listen to contacts changes
eventBus.register(ContactOperationEvent.class, e -> {
final var contact = e.get();
switch (e.getOperationType()) {
case ADD:
localDB.getUsers().put(contact.getName(), contact);
localDB.getChats().add(new Chat(contact));
Platform.runLater(() -> userList.getItems().add(contact));
break;
case REMOVE:
localDB.getUsers().remove(contact.getName());
localDB.getChats().removeIf(c -> c.getRecipient().getID() == contact.getID());
Platform.runLater(() -> userList.getItems().removeIf(c -> c.getID() == contact.getID()));
break;
}
});
}
/**
* Initializes all necessary data via dependency injection-
*
* @param sceneContext the scene context used to load other scenes
* @param localDB the local database form which chats and users are loaded
* @param client the client used to request ID generators
* @param writeProxy the write proxy used to send messages and other data to
* the server
* @since Envoy Client v0.1-beta
*/
public void initializeData(SceneContext sceneContext, LocalDB localDB, Client client, WriteProxy writeProxy) {
this.sceneContext = sceneContext;
this.localDB = localDB;
this.client = client;
this.writeProxy = writeProxy;
userList.setItems(FXCollections.observableList(localDB.getChats().stream().map(Chat::getRecipient).collect(Collectors.toList())));
}
/**
* Actions to perform when the list of contacts has been clicked.
*
* @since Envoy Client v0.1-beta
*/
@FXML
private void userListClicked() {
final Contact user = userList.getSelectionModel().getSelectedItem();
if (user != null && (currentChat == null || user.getID() != currentChat.getRecipient().getID())) {
contactLabel.setText(user.getName());
// LEON: JFC <===> JAVA FRIED CHICKEN <=/=> Java Foundation Classes
// Load the chat or create a new one and add it to the LocalDB
currentChat = localDB.getChats()
.stream()
.filter(c -> c.getRecipient().getID() == user.getID())
.findAny()
.orElseGet(() -> { final var chat = new Chat(user); localDB.getChats().add(chat); return chat; });
messageList.setItems(FXCollections.observableList(currentChat.getMessages()));
remainingChars.setVisible(true);
remainingChars
.setText(String.format("remaining chars: %d/%d", MAX_MESSAGE_LENGTH - messageTextArea.getText().length(), MAX_MESSAGE_LENGTH));
}
messageTextArea.setDisable(currentChat == null);
}
/**
* Actions to perform when the Post Button has been clicked.
*
* @since Envoy Client v0.1-beta
*/
@FXML
private void postButtonClicked() { postMessage(); }
/**
* Actions to perform when the Settings Button has been clicked.
*
* @since Envoy Client v0.1-beta
*/
@FXML
private void settingsButtonClicked() {
sceneContext.load(SceneContext.SceneInfo.SETTINGS_SCENE);
sceneContext.<SettingsScene>getController().initializeData(sceneContext);
}
/**
* Actions to perform when the "Add Contact" - Button has been clicked.
*
* @since Envoy Client v0.1-beta
*/
@FXML
private void addContactButtonClicked() {
sceneContext.load(SceneContext.SceneInfo.CONTACT_SEARCH_SCENE);
sceneContext.<ContactSearchScene>getController().initializeData(sceneContext);
}
/**
* Actions to perform when the text was updated in the messageTextArea.
*
* @since Envoy Client v0.1-beta
*/
@FXML
private void messageTextUpdated() {
// Truncating messages that are too long and staying at the same position
if (messageTextArea.getText().length() >= MAX_MESSAGE_LENGTH) {
messageTextArea.setText(messageTextArea.getText().substring(0, MAX_MESSAGE_LENGTH));
messageTextArea.positionCaret(MAX_MESSAGE_LENGTH);
messageTextArea.setScrollTop(Double.MAX_VALUE);
}
// Redesigning the remainingChars - Label
final int currentLength = messageTextArea.getText().length();
final int remainingLength = MAX_MESSAGE_LENGTH - currentLength;
remainingChars.setText(String.format("remaining chars: %d/%d", remainingLength, MAX_MESSAGE_LENGTH));
remainingChars.setTextFill(Color.rgb(currentLength, remainingLength, 0, 1));
}
/**
* Actions to perform when a key has been entered.
*
* @param e the Keys that have been entered
* @since Envoy Client v0.1-beta
*/
@FXML
private void checkKeyCombination(KeyEvent e) {
// Automatic sending of messages via (ctrl +) enter
if (!postButton.isDisabled() && settings.isEnterToSend() && e.getCode() == KeyCode.ENTER
|| !settings.isEnterToSend() && e.getCode() == KeyCode.ENTER && e.isControlDown())
postMessage();
postButton.setDisable(messageTextArea.getText().isBlank() || currentChat == null);
}
/**
* Sends a new message to the server based on the text entered in the
* messageTextArea.
*
* @since Envoy Client v0.1-beta
*/
private void postMessage() {
// Create and send message
sendMessage(new MessageBuilder(localDB.getUser().getID(), currentChat.getRecipient().getID(), localDB.getIDGenerator())
.setText(messageTextArea.getText().strip())
.build());
// Clear text field and disable post button
messageTextArea.setText("");
postButton.setDisable(true);
}
/**
* Sends a message to the server and appends it to the current chat. If all
* message IDs have been used, a new ID generator is requested.
*
* @param message the message to send
* @since Envoy Client v0.1-beta
*/
private void sendMessage(Message message) {
try {
// Send message
writeProxy.writeMessage(message);
// Add message to LocalDB and update UI
messageList.getItems().add(message);
// Request a new ID generator if all IDs were used
if (!localDB.getIDGenerator().hasNext() && client.isOnline()) client.requestIdGenerator();
} catch (final IOException e) {
logger.log(Level.SEVERE, "Error sending message", e);
}
}
}

View File

@ -0,0 +1,127 @@
package envoy.client.ui.controller;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.control.Alert.AlertType;
import envoy.client.event.SendEvent;
import envoy.client.ui.SceneContext;
import envoy.client.ui.ContactListCell;
import envoy.data.Contact;
import envoy.event.ElementOperation;
import envoy.event.EventBus;
import envoy.event.contact.ContactOperationEvent;
import envoy.event.contact.ContactSearchRequest;
import envoy.event.contact.ContactSearchResult;
import envoy.util.EnvoyLog;
/**
* Project: <strong>envoy-client</strong><br>
* File: <strong>ContactSearchSceneController.java</strong><br>
* Created: <strong>07.06.2020</strong><br>
*
* @author Leon Hofmeister
* @since Envoy Client v0.1-beta
*/
public class ContactSearchScene {
@FXML
private Button backButton;
@FXML
private Button clearButton;
@FXML
private Button searchButton;
@FXML
private TextField searchBar;
@FXML
private ListView<Contact> contactList;
private SceneContext sceneContext;
private static EventBus eventBus = EventBus.getInstance();
private static final Logger logger = EnvoyLog.getLogger(ChatScene.class);
/**
* @param sceneContext enables the user to return to the chat scene
* @since Envoy Client v0.1-beta
*/
public void initializeData(SceneContext sceneContext) { this.sceneContext = sceneContext; }
@FXML
private void initialize() {
contactList.setCellFactory(e -> new ContactListCell());
eventBus.register(ContactSearchResult.class, response -> Platform.runLater(() -> {
contactList.getItems().clear();
contactList.getItems().addAll(response.get());
}));
}
/**
* Disables the clear and search button if no text is present in the search bar.
*
* @since Envoy Client v0.1-beta
*/
@FXML
private void checkClearButton() {
final var containsContent = searchBar.getText().strip().isEmpty();
clearButton.setDisable(containsContent);
searchButton.setDisable(containsContent);
}
/**
* Sends a {@link ContactSearchRequest} to the server.
*
* @since Envoy Client v0.1-beta
*/
@FXML
private void suggestContacts() { eventBus.dispatch(new SendEvent(new ContactSearchRequest(searchBar.getText()))); }
/**
* Clears the text in the search bar and the items shown in the list.
* Additionally disables both clear and search button.
*
* @since Envoy Client v0.1-beta
*/
@FXML
private void clear() {
searchBar.setText(null);
contactList.getItems().clear();
clearButton.setDisable(true);
searchButton.setDisable(true);
}
/**
* Sends an {@link ContactOperationEvent} for every selected contact to the
* server.
*
* @since Envoy Client v0.1-beta
*/
@FXML
private void contactListClicked() {
final var contact = contactList.getSelectionModel().getSelectedItem();
if (contact != null) {
final var alert = new Alert(AlertType.CONFIRMATION);
alert.setTitle("Add Contact to Contact List");
alert.setHeaderText("Add the user " + contact.getName() + " to your contact list?");
alert.showAndWait().filter(btn -> btn == ButtonType.OK).ifPresent(btn -> {
final var event = new ContactOperationEvent(contact, ElementOperation.ADD);
// Sends the event to the server
eventBus.dispatch(new SendEvent(event));
// Updates the UI
eventBus.dispatch(event);
logger.log(Level.INFO, "Added contact " + contact);
});
}
}
@FXML
private void backButtonClicked() { sceneContext.pop(); }
}

View File

@ -0,0 +1,202 @@
package envoy.client.ui.controller;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
import java.util.logging.Logger;
import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.control.Alert.AlertType;
import envoy.client.data.Cache;
import envoy.client.data.ClientConfig;
import envoy.client.data.LocalDB;
import envoy.client.net.Client;
import envoy.client.ui.SceneContext;
import envoy.data.LoginCredentials;
import envoy.data.Message;
import envoy.data.User;
import envoy.data.User.UserStatus;
import envoy.event.EventBus;
import envoy.event.HandshakeRejectionEvent;
import envoy.exception.EnvoyException;
import envoy.util.EnvoyLog;
/**
* Project: <strong>envoy-client</strong><br>
* File: <strong>LoginDialog.java</strong><br>
* Created: <strong>03.04.2020</strong><br>
*
* @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta
*/
public final class LoginScene {
@FXML
private TextField userTextField;
@FXML
private PasswordField passwordField;
@FXML
private PasswordField repeatPasswordField;
@FXML
private Label repeatPasswordLabel;
@FXML
private CheckBox registerCheckBox;
@FXML
private Label connectionLabel;
private Client client;
private LocalDB localDB;
private Cache<Message> receivedMessageCache;
private SceneContext sceneContext;
private static final Logger logger = EnvoyLog.getLogger(LoginScene.class);
private static final EventBus eventBus = EventBus.getInstance();
private static final ClientConfig config = ClientConfig.getInstance();
@FXML
private void initialize() {
connectionLabel.setText("Server: " + config.getServer() + ":" + config.getPort());
// Show an alert after an unsuccessful handshake
eventBus.register(HandshakeRejectionEvent.class,
e -> Platform.runLater(() -> { clearPasswordFields(); new Alert(AlertType.ERROR, e.get()).showAndWait(); }));
}
/**
* Loads the login dialog using the FXML file {@code LoginDialog.fxml}.
*
* @param client the client used to perform the handshake
* @param localDB the local database used for offline login
* @param receivedMessageCache the cache storing messages received during
* the handshake
* @param sceneContext the scene context used to initialize the chat
* scene
* @since Envoy Client v0.1-beta
*/
public void initializeData(Client client, LocalDB localDB, Cache<Message> receivedMessageCache, SceneContext sceneContext) {
this.client = client;
this.localDB = localDB;
this.receivedMessageCache = receivedMessageCache;
this.sceneContext = sceneContext;
// Prepare handshake
localDB.loadIDGenerator();
// Set initial cursor
userTextField.requestFocus();
// Perform automatic login if configured
if (config.hasLoginCredentials()) performHandshake(config.getLoginCredentials());
}
@FXML
private void loginButtonPressed() {
// Prevent registration with unequal passwords
if (registerCheckBox.isSelected() && !passwordField.getText().equals(repeatPasswordField.getText())) {
clearPasswordFields();
new Alert(AlertType.ERROR, "The entered password is unequal to the repeated one").showAndWait();
} else performHandshake(new LoginCredentials(userTextField.getText(), passwordField.getText().toCharArray(), registerCheckBox.isSelected()));
}
@FXML
private void offlineModeButtonPressed() {
attemptOfflineMode(new LoginCredentials(userTextField.getText(), passwordField.getText().toCharArray(), false));
}
@FXML
private void registerCheckboxChanged() {
// Make repeat password field and label visible / invisible
repeatPasswordField.setVisible(registerCheckBox.isSelected());
repeatPasswordLabel.setVisible(registerCheckBox.isSelected());
clearPasswordFields();
}
@FXML
private void abortLogin() {
logger.info("The login process has been cancelled. Exiting...");
System.exit(0);
}
private void performHandshake(LoginCredentials credentials) {
try {
client.performHandshake(credentials, receivedMessageCache);
if (client.isOnline()) {
client.initReceiver(localDB, receivedMessageCache);
loadChatScene();
}
} catch (IOException | InterruptedException | TimeoutException e) {
logger.warning("Could not connect to server: " + e);
logger.finer("Attempting offline mode...");
attemptOfflineMode(credentials);
}
}
private void attemptOfflineMode(LoginCredentials credentials) {
try {
// Try entering offline mode
localDB.loadUsers();
final User clientUser = (User) localDB.getUsers().get(credentials.getIdentifier());
if (clientUser == null) throw new EnvoyException("Could not enter offline mode: user name unknown");
client.setSender(clientUser);
new Alert(AlertType.WARNING, "A connection to the server could not be established. Starting in offline mode.").showAndWait();
loadChatScene();
} catch (final Exception e) {
new Alert(AlertType.ERROR, "Client error: " + e).showAndWait();
System.exit(1);
}
}
private void loadChatScene() {
// Set client user in local database
localDB.setUser(client.getSender());
// Initialize chats in local database
try {
localDB.initializeUserStorage();
localDB.loadUserData();
} catch (final FileNotFoundException e) {
// The local database file has not yet been created, probably first login
} catch (final Exception e) {
e.printStackTrace();
new Alert(AlertType.ERROR, "Error while loading local database: " + e + "\nChats will not be stored locally.").showAndWait();
}
// Initialize write proxy
final var writeProxy = client.createWriteProxy(localDB);
if (client.isOnline()) {
// Save all users to the local database and flush cache
localDB.setUsers(client.getUsers());
writeProxy.flushCache();
} else
// Set all contacts to offline mode
localDB.getUsers().values().stream().filter(User.class::isInstance).map(User.class::cast).forEach(u -> u.setStatus(UserStatus.OFFLINE));
// Load ChatScene
sceneContext.pop();
sceneContext.getStage().setMinHeight(400);
sceneContext.getStage().setMinWidth(350);
sceneContext.load(SceneContext.SceneInfo.CHAT_SCENE);
sceneContext.<ChatScene>getController().initializeData(sceneContext, localDB, client, writeProxy);
// Relay unread messages from cache
if (receivedMessageCache != null && client.isOnline()) receivedMessageCache.relay();
}
private void clearPasswordFields() {
passwordField.clear();
repeatPasswordField.clear();
}
}

View File

@ -0,0 +1,59 @@
package envoy.client.ui.controller;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import envoy.client.ui.SceneContext;
import envoy.client.ui.settings.GeneralSettingsPane;
import envoy.client.ui.settings.SettingsPane;
/**
* Project: <strong>envoy-client</strong><br>
* File: <strong>SettingsSceneController.java</strong><br>
* Created: <strong>10.04.2020</strong><br>
*
* @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta
*/
public class SettingsScene {
@FXML
private ListView<SettingsPane> settingsList;
@FXML
private TitledPane titledPane;
private SceneContext sceneContext;
/**
* @param sceneContext enables the user to return to the chat scene
* @since Envoy Client v0.1-beta
*/
public void initializeData(SceneContext sceneContext) { this.sceneContext = sceneContext; }
@FXML
private void initialize() {
settingsList.setCellFactory(listView -> new ListCell<>() {
@Override
protected void updateItem(SettingsPane item, boolean empty) {
super.updateItem(item, empty);
if (!empty && item != null) setGraphic(new Label(item.getTitle()));
}
});
settingsList.getItems().add(new GeneralSettingsPane());
}
@FXML
private void settingsListClicked() {
final var pane = settingsList.getSelectionModel().getSelectedItem();
if (pane != null) {
titledPane.setText(pane.getTitle());
titledPane.setContent(pane);
}
}
@FXML
private void backButtonClicked() { sceneContext.pop(); }
}

View File

@ -0,0 +1,11 @@
/**
* Contains JavaFX scene controllers.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>package-info.java</strong><br>
* Created: <strong>08.06.2020</strong><br>
*
* @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta
*/
package envoy.client.ui.controller;

View File

@ -1,261 +0,0 @@
package envoy.client.ui.list;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.HashSet;
import java.util.Set;
import javax.swing.*;
/**
* Provides a vertical list layout of components provided in a
* {@link Model}. Similar to {@link javax.swing.JList} but capable
* of rendering {@link JPanel}s.<br>
* <br>
* Project: <strong>envoy-client</strong><br>
* File: <strong>ComponentList.java</strong><br>
* Created: <strong>25.01.2020</strong><br>
*
* @param <E> the type of object displayed in this list
* @author Kai S. K. Engelbart
* @since Envoy Client v0.3-alpha
*/
public class ComponentList<E> extends JPanel {
private Model<E> model;
private Renderer<E> renderer;
private SelectionHandler<E> selectionHandler;
private SelectionMode selectionMode = SelectionMode.NONE;
private Set<Integer> selection = new HashSet<>();
private static final long serialVersionUID = 0L;
/**
* Defines the possible modes of selection that can be performed by the user
*
* @since Envoy Client v0.1-beta
*/
public static enum SelectionMode {
/**
* Selection is completely ignored.
*/
NONE,
/**
* Only a single element can be selected.
*/
SINGLE,
/**
* Multiple elements can be selected regardless of their position.
*/
MULTIPLE
}
/**
* Creates an instance of {@link ComponentList}.
*
* @since Envoy Client v0.3-alpha
*/
public ComponentList() { setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); }
/**
* Removes all child components and then adds all components representing the
* elements of the {@link Model}.
*
* @since Envoy Client v0.3-alpha
*/
public void synchronizeModel() {
if (model != null) {
removeAll();
model.forEach(this::addElement);
revalidate();
}
}
/**
* Selects a list element by index. If the element is already selected, it is
* removed from the selection.
*
* @param index the index of the selected component
* @since Envoy Client v0.1-beta
*/
public void selectElement(int index) {
final JComponent element = getComponent(index);
if (selection.contains(index)) {
// Deselect if clicked again
if (selectionHandler != null) selectionHandler.selectionChanged(model.get(index), element, true);
selection.remove(index);
} else {
// Remove old selection if single selection is enabled
if (selectionMode == SelectionMode.SINGLE) clearSelection();
// Select item
if (selectionMode != SelectionMode.NONE) {
// Assign new selection
selection.add(index);
// Update element
if (selectionHandler != null) selectionHandler.selectionChanged(model.get(index), element, true);
}
}
revalidate();
repaint();
}
/**
* Removes the current selection.
*
* @since Envoy Client v0.1-alpha
*/
public void clearSelection() {
if (selectionHandler != null) selection.forEach(i -> selectionHandler.selectionChanged(model.get(i), getComponent(i), false));
selection.clear();
}
/**
* Adds an object to the list by rendering it with the current
* {@link Renderer}.
*
* @param elem the element to add
* @since Envoy Client v0.3-alpha
*/
void addElement(E elem) {
if (renderer != null) {
final JComponent component = renderer.getListCellComponent(this, elem);
component.addMouseListener(getSelectionListener(getComponentCount()));
add(component, getComponentCount());
}
}
/**
* @param componentIndex the index of the list component to which the mouse
* listener will be added
* @return a mouse listener calling the
* {@link ComponentList#selectElement(int)} method with the
* component's index when a left click is performed by the user
* @since Envoy Client v0.1-beta
*/
private MouseListener getSelectionListener(int componentIndex) {
return new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) { if (SwingUtilities.isLeftMouseButton(e)) selectElement(componentIndex); }
};
}
@Override
public JComponent getComponent(int n) { return (JComponent) super.getComponent(n); }
/**
* @return a set of all selected indices
* @since Envoy Client v0.1-beta
*/
public Set<Integer> getSelection() { return selection; }
/**
* @return a set of all selected elements
* @since Envoy Client v0.1-beta
*/
public Set<E> getSelectedElements() {
var selectedElements = new HashSet<E>();
selection.forEach(i -> selectedElements.add(model.get(i)));
return selectedElements;
}
/**
* @return the index of an arbitrary selected element
* @throws java.util.NoSuchElementException if no selection is present
* @since Envoy Client v0.1-beta
*/
public int getSingleSelection() { return selection.stream().findAny().get(); }
/**
* @return an arbitrary selected element
* @throws java.util.NoSuchElementException if no selection is present
* @since Envoy Client v0.1-beta
*/
public E getSingleSelectedElement() { return model.get(getSingleSelection()); }
/**
* @return the model
* @since Envoy Client v0.1-beta
*/
public Model<E> getModel() { return model; }
/**
* Sets the list model providing the list elements to render. The rendered
* components will be synchronized with the contents of the new model or removed
* if the new model is {@code null}.
*
* @param model the list model to set
* @return this component list
* @since Envoy Client v0.3-alpha
*/
public ComponentList<E> setModel(Model<E> model) {
// Remove old model
if (this.model != null) this.model.setComponentList(null);
// Synchronize with new model
this.model = model;
if (model != null) model.setComponentList(this);
synchronizeModel();
return this;
}
/**
* @return the renderer
* @since Envoy Client v0.1-beta
*/
public Renderer<E> getRenderer() { return renderer; }
/**
* @param renderer the renderer to set
* @return this component list
* @since Envoy Client v0.1-beta
*/
public ComponentList<E> setRenderer(Renderer<E> renderer) {
this.renderer = renderer;
return this;
}
/**
* @return the selection mode
* @since Envoy Client v0.1-beta
*/
public SelectionMode getSelectionMode() { return selectionMode; }
/**
* Sets a new selection mode. The current selection will be cleared during this
* action.
*
* @param selectionMode the selection mode to set
* @return this component list
* @since Envoy Client v0.1-beta
*/
public ComponentList<E> setSelectionMode(SelectionMode selectionMode) {
this.selectionMode = selectionMode;
clearSelection();
return this;
}
/**
* @return the selection handler
* @since Envoy Client v0.1-beta
*/
public SelectionHandler<E> getSelectionHandler() { return selectionHandler; }
/**
* @param selectionHandler the selection handler to set
* @since Envoy Client v0.1-beta
*/
public void setSelectionHandler(SelectionHandler<E> selectionHandler) { this.selectionHandler = selectionHandler; }
}

View File

@ -1,120 +0,0 @@
package envoy.client.ui.list;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* Stores objects that will be displayed in a {@link ComponentList}.<br>
* <br>
* Project: <strong>envoy-client</strong><br>
* File: <strong>Model.java</strong><br>
* Created: <strong>25.01.2020</strong><br>
*
* @param <E> the type of object displayed in this list
* @author Kai S. K. Engelbart
* @since Envoy Client v0.3-alpha
*/
public final class Model<E> implements Iterable<E>, Serializable {
private List<E> elements = new ArrayList<>();
transient private ComponentList<E> componentList;
private static final long serialVersionUID = 0L;
/**
* Adds an element to this model and notifies the associated
* {@link ComponentList} to add the corresponding component.
*
* @param e the element to add
* @return {@code true}
* @see java.util.List#add(java.lang.Object)
* @since Envoy Client v0.3-alpha
*/
public boolean add(E e) {
if (componentList != null) {
componentList.addElement(e);
componentList.revalidate();
}
return elements.add(e);
}
/**
* Removes all elements from this model and clears the associated
* {@link ComponentList}.
*
* @see java.util.List#clear()
* @since Envoy Client v0.3-alpha
*/
public void clear() {
elements.clear();
if (componentList != null) componentList.removeAll();
}
/**
* @param index the index to retrieve the element from
* @return the element located at the index
* @see java.util.List#get(int)
* @since Envoy Client v0.3-alpha
*/
public E get(int index) { return elements.get(index); }
/**
* Removes the element at a specific index from this model and the corresponding
* component from the {@link ComponentList}.
*
* @param index the index of the element to remove
* @return the removed element
* @see java.util.List#remove(int)
* @since Envoy Client v0.3-alpha
*/
public E remove(int index) {
if (componentList != null) componentList.remove(index);
return elements.remove(index);
}
/**
* @return the amount of elements in this list model
* @see java.util.List#size()
* @since Envoy Client v0.3-alpha
*/
public int size() { return elements.size(); }
/**
* @return {@code true} if this model contains no elements
* @see java.util.List#isEmpty()
*/
public boolean isEmpty() { return elements.isEmpty(); }
/**
* @return an iterator over the elements of this list model
* @see java.util.List#iterator()
* @since Envoy Client v0.3-alpha
*/
@Override
public Iterator<E> iterator() {
return new Iterator<>() {
Iterator<E> iter = elements.iterator();
@Override
public boolean hasNext() { return iter.hasNext(); }
@Override
public E next() { return iter.next(); }
};
}
/**
* Sets the component list displaying the elements of this model and triggers a
* synchronization.
*
* @param componentList the component list to set
* @since Envoy Client v0.3-alpha
*/
void setComponentList(ComponentList<E> componentList) {
this.componentList = componentList;
if (componentList != null && componentList.getRenderer() != null) componentList.synchronizeModel();
}
}

View File

@ -1,31 +0,0 @@
package envoy.client.ui.list;
import javax.swing.JComponent;
/**
* Allows a {@link ComponentList} convert its elements into Swing components
* that can be rendered.<br>
* <br>
* Project: <strong>envoy-client</strong><br>
* File: <strong>Renderer.java</strong><br>
* Created: <strong>25.01.2020</strong><br>
*
* @param <E> the type of object displayed in this list
* @author Kai S. K. Engelbart
* @since Envoy Client v0.3-alpha
*/
@FunctionalInterface
public interface Renderer<E> {
/**
* Provides a Swing component representing a list element.
*
* @param list the list in which the component will be displayed
* @param value the list element that will be converted
* @param isSelected {@code true} if the user has selected the list cell in
* which the list element is rendered
* @return the component representing the list element
* @since Envoy Client v0.3-alpha
*/
JComponent getListCellComponent(ComponentList<? extends E> list, E value);
}

View File

@ -1,28 +0,0 @@
package envoy.client.ui.list;
import javax.swing.JComponent;
/**
* Handles the selection of elements in a {@link ComponentList}.<br>
* <br>
* Project: <strong>envoy-client</strong>
* File: <strong>SelectionHandler.java</strong>
* Created: <strong>21.03.2020</strong>
*
* @author Kai S. K. Engelbart
* @param <E> the type of the underlying {@link ComponentList}
* @since Envoy Client v0.1-beta
*/
@FunctionalInterface
public interface SelectionHandler<E> {
/**
* Notifies the handler about a selection.
*
* @param element the selected element
* @param component the selected component
* @param isSelected contains the selection state
* @since Envoy Client v0.1-beta
*/
void selectionChanged(E element, JComponent component, boolean isSelected);
}

View File

@ -1,10 +0,0 @@
/**
* This package defines a Swing component that can be used to display lists of
* other components to the user.
*
* @author Kai S. K. Engelbart
* @author Leon Hofmeister
* @author Maximilian K&auml;fer
* @since Envoy Client v0.3-alpha
*/
package envoy.client.ui.list;

View File

@ -1,73 +0,0 @@
package envoy.client.ui.list_component;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import javax.swing.*;
import envoy.client.data.Settings;
import envoy.client.event.SendEvent;
import envoy.client.ui.list.ComponentList;
import envoy.client.ui.primary.PrimaryButton;
import envoy.data.User;
import envoy.event.ContactOperationEvent;
import envoy.event.EventBus;
/**
* Project: <strong>envoy-client</strong>
* File: <strong>ContactSearchComponent.java</strong>
* Created: <strong>21.03.2020</strong>
*
* @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta
*/
public class ContactSearchComponent extends JComponent {
private static final long serialVersionUID = 0L;
/**
* @param list the {@link ComponentList} that is used to display search results
* @param user the {@link User} that appears as a search result
* @since Envoy Client v0.1-beta
*/
public ContactSearchComponent(ComponentList<? extends User> list, User user) {
setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
setBackground(list.getBackground());
setForeground(list.getForeground());
JLabel display = new JLabel(user.getName());
display.setForeground(Settings.getInstance().getCurrentTheme().getTextColor());
display.setAlignmentX(Component.LEFT_ALIGNMENT);
display.setAlignmentY(Component.CENTER_ALIGNMENT);
display.setFont(new Font("Arial", Font.PLAIN, 16));
add(display);
PrimaryButton add = new PrimaryButton("+");
add.setFont(new Font("Arial", Font.PLAIN, 19));
add.setPreferredSize(new Dimension(45, 45));
add.setMinimumSize(new Dimension(45, 45));
add.setMaximumSize(new Dimension(45, 45));
add.setBackground(list.getBackground());
add.setForeground(list.getForeground());
add.addActionListener(evt -> {
ContactOperationEvent contactsOperationEvent = new ContactOperationEvent(user, ContactOperationEvent.Operation.ADD);
EventBus.getInstance().dispatch(contactsOperationEvent);
EventBus.getInstance().dispatch(new SendEvent(contactsOperationEvent));
});
add(add);
// Define some space to the messages below
setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(0, 0, 15, 0), BorderFactory.createEtchedBorder()));
// Define a maximum height of 50px
Dimension size = new Dimension(435, 50);
setMaximumSize(size);
setMinimumSize(size);
setPreferredSize(size);
}
}

View File

@ -1,127 +0,0 @@
package envoy.client.ui.list_component;
import java.awt.*;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.EnumMap;
import javax.swing.*;
import envoy.client.data.Chat;
import envoy.client.data.Settings;
import envoy.client.ui.Color;
import envoy.client.ui.IconUtil;
import envoy.client.ui.list.ComponentList;
import envoy.data.Message;
import envoy.data.Message.MessageStatus;
import envoy.data.User;
/**
* Project: <strong>envoy-client</strong>
* File: <strong>MessageComponent.java</strong>
* Created: <strong>21.03.2020</strong>
*
* @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta
*/
public class MessageComponent extends JPanel {
private static final long serialVersionUID = 0L;
private static EnumMap<MessageStatus, ImageIcon> statusIcons;
private static ImageIcon forwardIcon;
static {
try {
statusIcons = IconUtil.loadByEnum(MessageStatus.class, 16);
forwardIcon = IconUtil.load("/icons/forward.png", 16);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* @param list the {@link ComponentList} that displays this {@link Chat}
* @param message the {@link Message} to display
* @param senderId the id of the {@link User} who sends messages from this
* account
* @since Envoy Client v0.1-beta
*/
public MessageComponent(ComponentList<? extends Message> list, Message message, long senderId) {
var width = list.getMaximumSize().width;
final var theme = Settings.getInstance().getCurrentTheme();
final int padding = (int) (width * 0.35);
GridBagLayout gbl_panel = new GridBagLayout();
gbl_panel.columnWidths = new int[] { 1, 1 };
gbl_panel.rowHeights = new int[] { 1, 1 };
gbl_panel.columnWeights = new double[] { 1, 1 };
gbl_panel.rowWeights = new double[] { 1, 1 };
setLayout(gbl_panel);
setBackground(theme.getCellColor());
// Date Label - The Label that displays the creation date of a message
var dateLabel = new JLabel(new SimpleDateFormat("dd.MM.yyyy HH:mm").format(message.getCreationDate()));
dateLabel.setForeground(theme.getDateColor());
dateLabel.setAlignmentX(1f);
dateLabel.setFont(new Font("Arial", Font.PLAIN, 12));
dateLabel.setPreferredSize(dateLabel.getPreferredSize());
var gbc_dateLabel = new GridBagConstraints();
gbc_dateLabel.fill = GridBagConstraints.BOTH;
gbc_dateLabel.gridx = 0;
gbc_dateLabel.gridy = 0;
add(dateLabel, gbc_dateLabel);
// Message area - The JTextArea that displays the text content of a message.
var messageTextArea = new JTextArea(message.getText());
messageTextArea.setLineWrap(true);
messageTextArea.setWrapStyleWord(true);
messageTextArea.setForeground(theme.getTextColor());
messageTextArea.setAlignmentX(0.5f);
messageTextArea.setBackground(theme.getCellColor());
messageTextArea.setEditable(false);
var font = new Font("Arial", Font.PLAIN, 14);
messageTextArea.setFont(font);
messageTextArea.setSize(width - padding - 16, 10);
var gbc_messageTextArea = new GridBagConstraints();
gbc_messageTextArea.fill = GridBagConstraints.HORIZONTAL;
gbc_messageTextArea.gridx = 0;
gbc_messageTextArea.gridy = 1;
add(messageTextArea, gbc_messageTextArea);
// Status Label - displays the status of the message
var statusLabel = new JLabel(statusIcons.get(message.getStatus()));
var gbc_statusLabel = new GridBagConstraints();
gbc_statusLabel.gridx = 1;
gbc_statusLabel.gridy = 1;
add(statusLabel, gbc_statusLabel);
// Forwarding
if (message.isForwarded()) {
var forwardLabel = new JLabel("Forwarded", forwardIcon, SwingConstants.CENTER);
forwardLabel.setBackground(getBackground());
forwardLabel.setForeground(Color.lightGray);
var gbc_forwardLabel = new GridBagConstraints();
gbc_forwardLabel.fill = GridBagConstraints.BOTH;
gbc_forwardLabel.gridx = 1;
gbc_forwardLabel.gridy = 0;
add(forwardLabel, gbc_forwardLabel);
}
// Define an etched border and some space to the messages below
var ours = senderId == message.getSenderID();
setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(0, ours ? padding : 10, 10, ours ? 0 : padding),
BorderFactory.createEtchedBorder()));
var size = new Dimension(width - 50, getPreferredSize().height);
setPreferredSize(size);
setMinimumSize(size);
setMaximumSize(size);
}
}

View File

@ -1,69 +0,0 @@
package envoy.client.ui.list_component;
import java.awt.BorderLayout;
import java.awt.Dimension;
import javax.swing.JLabel;
import javax.swing.JPanel;
import envoy.client.data.Settings;
import envoy.client.ui.Color;
import envoy.client.ui.Theme;
import envoy.data.User;
import envoy.data.User.UserStatus;
/**
* Displays a {@link User}.<br>
* <br>
* Project: <strong>envoy-client</strong>
* File: <strong>UserComponent.java</strong>
* Created: <strong>21.03.2020</strong>
*
* @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta
*/
public class UserComponent extends JPanel {
private static final long serialVersionUID = 0L;
/**
* @param user the {@link User} whose information is displayed
* @since Envoy Client v0.1-beta
*/
public UserComponent(User user) {
final Theme theme = Settings.getInstance().getCurrentTheme();
setLayout(new BorderLayout());
// Panel background
setBackground(theme.getCellColor());
setOpaque(true);
setPreferredSize(new Dimension(100, 35));
// TODO add profile picture support in BorderLayout.West
JLabel username = new JLabel(user.getName());
username.setForeground(theme.getUserNameColor());
add(username, BorderLayout.CENTER);
final UserStatus status = user.getStatus();
JLabel statusLabel = new JLabel(status.toString());
Color foreground;
switch (status) {
case AWAY:
foreground = Color.yellow;
break;
case BUSY:
foreground = Color.blue;
break;
case ONLINE:
foreground = Color.green;
break;
default:
foreground = Color.lightGray;
break;
}
statusLabel.setForeground(foreground);
add(statusLabel, BorderLayout.NORTH);
}
}

View File

@ -1,14 +0,0 @@
/**
* This package contains swing components that can be displayed by
* {@link envoy.client.ui.list.ComponentList}.<br>
* <br>
* Project: <strong>envoy-client</strong><br>
* File: <strong>package-info.java</strong><br>
* Created: <strong>21 Mar 2020</strong><br>
*
* @author Leon Hofmeister
* @author Kai S. K. Engelbart
* @author Maximilian K&auml;fer
* @since Envoy Client v0.1-beta
*/
package envoy.client.ui.list_component;

View File

@ -1,8 +1,8 @@
/**
* This package contains classes defining the user interface.
*
* @author Kai S. K. Engelbart
* @author Leon Hofmeister
* @author Kai S. K. Engelbart
* @author Maximilian K&auml;fer
* @since Envoy Client v0.1-beta
*/

View File

@ -1,63 +0,0 @@
package envoy.client.ui.primary;
import java.awt.Graphics;
import javax.swing.JButton;
/**
* Project: <strong>envoy-client</strong><br>
* File: <strong>PrimaryButton.javaEvent.java</strong><br>
* Created: <strong>07.12.2019</strong><br>
*
* @author Kai S. K. Engelbart
* @author Maximilian K&auml;fer
* @since Envoy Client v0.2-alpha
*/
public class PrimaryButton extends JButton {
private static final long serialVersionUID = 0L;
private int arcSize;
/**
* Creates a primary button
*
* @param title the title of the button
* @since Envoy 0.2-alpha
*/
public PrimaryButton(String title) { this(title, 6); }
/**
* Creates a primary button
*
* @param title the title of the button
* @param arcSize the size of the arc used to draw the round button edges
* @since Envoy 0.2-alpha
*/
public PrimaryButton(String title, int arcSize) {
super(title);
setBorderPainted(false);
setFocusPainted(false);
setContentAreaFilled(false);
this.arcSize = arcSize;
}
@Override
protected void paintComponent(Graphics g) {
g.setColor(getBackground());
g.fillRoundRect(0, 0, getWidth(), getHeight(), arcSize, arcSize);
super.paintComponent(g);
}
/**
* @return the arcSize
* @since Envoy 0.2-alpha
*/
public int getArcSize() { return arcSize; }
/**
* @param arcSize the arcSize to set
* @since Envoy 0.2-alpha
*/
public void setArcSize(int arcSize) { this.arcSize = arcSize; }
}

View File

@ -1,126 +0,0 @@
package envoy.client.ui.primary;
import java.awt.*;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JScrollBar;
import javax.swing.plaf.basic.BasicScrollBarUI;
import envoy.client.data.Settings;
import envoy.client.ui.Theme;
/**
* Project: <strong>envoy-client</strong><br>
* File: <strong>PrimaryScrollBar.java</strong><br>
* Created: <strong>14.12.2019</strong><br>
*
* @author Maximilian K&auml;fer
* @since Envoy Client v0.2-alpha
*/
public class PrimaryScrollBar extends BasicScrollBarUI {
private final Dimension d = new Dimension();
private final int arcSize;
private final Color scrollBarColor;
private final Color hoverColor;
private final Color draggingColor;
private final boolean isVertical;
/**
* Initializes a {@link PrimaryScrollBar} with a color scheme.
*
* @param arcSize the size of the arc used to draw the round scroll bar
* edges
* @param scrollBarColor the default color
* @param hoverColor the color while hovering
* @param draggingColor the color while dragging
* @param isVertical indicates whether this is a vertical
* {@link PrimaryScrollBar}
*/
public PrimaryScrollBar(int arcSize, Color scrollBarColor, Color hoverColor, Color draggingColor, boolean isVertical) {
this.arcSize = arcSize;
this.scrollBarColor = scrollBarColor;
this.hoverColor = hoverColor;
this.draggingColor = draggingColor;
this.isVertical = isVertical;
}
/**
* Initializes a {@link PrimaryScrollBar} using a color scheme specified in a
* {@link Theme}
*
* @param theme the {@link Theme} to be applied to this
* {@link PrimaryScrollBar}
* @param isVertical indicates whether this is a vertical
* {@link PrimaryScrollBar}
*/
public PrimaryScrollBar(Theme theme, boolean isVertical) {
this(5, theme.getInteractableBackgroundColor(), new Color(theme.getInteractableBackgroundColor().getRGB() - 50),
new Color(theme.getInteractableBackgroundColor().getRGB() + 170), isVertical);
}
/**
* {@inheritDoc}
*/
@Override
protected JButton createDecreaseButton(int orientation) {
JButton button = new JButton();
button.setPreferredSize(d);
return button;
}
/**
* {@inheritDoc}
*/
@Override
protected JButton createIncreaseButton(int orientation) {
JButton button = new JButton();
button.setPreferredSize(d);
return button;
}
/**
* {@inheritDoc}
*/
@Override
protected void paintTrack(Graphics g, JComponent c, Rectangle r) {}
/**
* {@inheritDoc}
*/
@Override
protected void paintThumb(Graphics g, JComponent c, Rectangle r) {
Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
Color color;
JScrollBar sb = (JScrollBar) c;
if (!sb.isEnabled()) return;
if (isDragging) color = draggingColor;
else if (isThumbRollover()) color = hoverColor;
else color = scrollBarColor;
g2.setPaint(color);
if (isVertical) {
g2.fillRoundRect(r.x - 9, r.y, r.width, r.height, arcSize, arcSize);
g2.setPaint(Settings.getInstance().getCurrentTheme().getCellColor());
g2.drawRoundRect(r.x - 9, r.y, r.width, r.height, arcSize, arcSize);
} else {
g2.fillRoundRect(r.x, r.y + 9, r.width, r.height - 10, arcSize, arcSize);
g2.setPaint(Settings.getInstance().getCurrentTheme().getCellColor());
g2.drawRoundRect(r.x, r.y + 9, r.width, r.height - 10, arcSize, arcSize);
}
g2.dispose();
}
/**
* {@inheritDoc}
*/
@Override
protected void setThumbBounds(int x, int y, int width, int height) {
super.setThumbBounds(x, y, width, height);
scrollbar.repaint();
}
}

View File

@ -1,85 +0,0 @@
package envoy.client.ui.primary;
import javax.swing.JScrollPane;
import envoy.client.ui.Theme;
/**
* Project: <strong>envoy-client</strong><br>
* File: <strong>PrimaryScrollPane.java</strong><br>
* Created: <strong>15 Dec 2019</strong><br>
*
* @author Kai S. K. Engelbart
* @author Maximilian K&auml;fer
*/
public class PrimaryScrollPane extends JScrollPane {
private static final long serialVersionUID = 0L;
private int verticalScrollBarMaximum = getVerticalScrollBar().getMaximum();
private boolean chatOpened = false;
/**
* Initializes a {@link JScrollPane} with the primary Envoy design scheme
*
* @since Envoy Client v0.2-alpha
*/
public PrimaryScrollPane() { setBorder(null); }
/**
* Styles the vertical and horizontal scroll bars.
*
* @param theme the color set used to color the component
* @since Envoy Client v0.2-alpha
*/
public void applyTheme(Theme theme) {
setForeground(theme.getBackgroundColor());
setBackground(theme.getCellColor());
getVerticalScrollBar().setBackground(theme.getCellColor());
getVerticalScrollBar().setUI(new PrimaryScrollBar(theme, true));
getHorizontalScrollBar().setBackground(theme.getCellColor());
getHorizontalScrollBar().setUI(new PrimaryScrollBar(theme, false));
}
/**
* Implements <b>autoscroll functionality</b> for the vertical scroll bar. </br>
* </br>
* Functionality to automatically scroll down when user views </br>
* the bottom of the chat while there are new messages added. </br>
* </br>
* When chat is opened, the vertical scroll bar starts at the bottom. </br>
* </br>
* When rereading messages, the chat doesn't scroll down if new messages </br>
* are added. (Besides see first point)
*
* @since Envoy Client v0.2-alpha
*/
public void autoscroll() {
// Automatic scrolling to the bottom
getVerticalScrollBar().addAdjustmentListener(e -> {
if (verticalScrollBarMaximum == e.getAdjustable().getMaximum()) return;
if (chatOpened) {
e.getAdjustable().setValue(e.getAdjustable().getMaximum());
verticalScrollBarMaximum = getVerticalScrollBar().getMaximum();
chatOpened = false;
return;
}
if (getVerticalScrollBar().getValue() + getVerticalScrollBar().getVisibleAmount() + 100 >= getVerticalScrollBar().getMaximum()) {
e.getAdjustable().setValue(e.getAdjustable().getMaximum());
verticalScrollBarMaximum = getVerticalScrollBar().getMaximum();
}
});
}
/**
* Indicates a chat being opened by the user to this {@link PrimaryScrollPane}
* triggering it to automatically scroll down.
*
* @param chatOpened indicates the chat opening status
* @since Envoy Client v0.2-alpha
*/
public void setChatOpened(boolean chatOpened) { this.chatOpened = chatOpened; }
}

View File

@ -1,70 +0,0 @@
package envoy.client.ui.primary;
import java.awt.Font;
import java.awt.Graphics;
import javax.swing.JTextArea;
import javax.swing.border.EmptyBorder;
/**
* Project: <strong>envoy-client</strong><br>
* File: <strong>PrimaryTextArea.javaEvent.java</strong><br>
* Created: <strong>07.12.2019</strong><br>
*
* @author Maximilian K&auml;fer
* @since Envoy Client v0.2-alpha
*/
public class PrimaryTextArea extends JTextArea {
private static final long serialVersionUID = 0L;
private int arcSize;
/**
* Creates the text area
*
* @param borderSpace the space between components
* @since Envoy 0.2-alpha
*/
public PrimaryTextArea(int borderSpace) { this(6, borderSpace); }
/**
* Creates the text area
*
* @param arcSize is the diameter of the arc at the four corners.
* @param borderSpace is the insets of the border on all four sides.
* @since Envoy 0.2-alpha
*/
public PrimaryTextArea(int arcSize, int borderSpace) {
super();
setWrapStyleWord(true);
setLineWrap(true);
setBorder(null);
setFont(new Font("Arial", Font.PLAIN, 17));
setBorder(new EmptyBorder(borderSpace, borderSpace, borderSpace, borderSpace));
setOpaque(false);
this.arcSize = arcSize;
}
/**
* {@inheritDoc}
*/
@Override
protected void paintComponent(Graphics g) {
g.setColor(getBackground());
g.fillRoundRect(0, 0, getWidth(), getHeight(), arcSize, arcSize);
super.paintComponent(g);
}
/**
* @return the arcSize - the diameter of the arc at the four corners.
* @since Envoy 0.2-alpha
*/
public int getArcSize() { return arcSize; }
/**
* @param arcSize the arcSize to set
* @since Envoy 0.2-alpha
*/
public void setArcSize(int arcSize) { this.arcSize = arcSize; }
}

View File

@ -1,61 +0,0 @@
package envoy.client.ui.primary;
import java.awt.Dimension;
import java.awt.Graphics;
import javax.swing.JButton;
import envoy.client.data.Settings;
import envoy.client.data.SettingsItem;
import envoy.client.ui.Color;
/**
* This component can be used to toggle between two options. This will change
* the state of a {@code boolean} {@link SettingsItem}.<br>
* <br>
* Project: <strong>envoy-client</strong><br>
* File: <strong>PrimaryToggleSwitch.java</strong><br>
* Created: <strong>21 Dec 2019</strong><br>
*
* @author Maximilian K&auml;fer
* @author Kai S. K. Engelbart
* @since Envoy Client v0.3-alpha
*/
public class PrimaryToggleSwitch extends JButton {
private boolean state;
private static final long serialVersionUID = 0L;
/**
* Initializes a {@link PrimaryToggleSwitch}.
*
* @param settingsItem the {@link SettingsItem} that is controlled by this
* {@link PrimaryToggleSwitch}
* @since Envoy Client v0.3-alpha
*/
public PrimaryToggleSwitch(SettingsItem<Boolean> settingsItem) {
setPreferredSize(new Dimension(50, 25));
setMinimumSize(new Dimension(50, 25));
setMaximumSize(new Dimension(50, 25));
setBorderPainted(false);
setFocusPainted(false);
setContentAreaFilled(false);
state = settingsItem.get();
addActionListener((evt) -> { state = !state; settingsItem.set(state); revalidate(); repaint(); });
}
/**
* {@inheritDoc}
*/
@Override
public void paintComponent(Graphics g) {
g.setColor(state ? Color.GREEN : Color.LIGHT_GRAY);
g.fillRoundRect(0, 0, getWidth(), getHeight(), 25, 25);
g.setColor(Settings.getInstance().getCurrentTheme().getInteractableBackgroundColor());
g.fillRoundRect(state ? 25 : 0, 0, 25, 25, 25, 25);
}
}

View File

@ -1,17 +0,0 @@
/**
* This package defines all "primary" components that were defined specifically
* for the visual improvement of Envoy. However, they can still be used in
* general for other projects.<br>
* Primary elements are supposed to provide the main functionality of a UI
* component.<br>
* <br>
* Project: <strong>envoy-client</strong><br>
* File: <strong>package-info.java</strong><br>
* Created: <strong>14 Mar 2020</strong><br>
*
* @author Leon Hofmeister
* @author Kai S. K. Engelbart
* @author Maximilian K&auml;fer
* @since Envoy Client v0.1-beta
*/
package envoy.client.ui.primary;

View File

@ -1,65 +0,0 @@
package envoy.client.ui.renderer;
import java.awt.Component;
import java.awt.Dimension;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.ListCellRenderer;
import envoy.client.data.Settings;
import envoy.data.User;
import envoy.data.User.UserStatus;
/**
* Defines how the {@code UserList} is displayed.<br>
* <br>
* Project: <strong>envoy-client</strong><br>
* File: <strong>UserListRenderer.java</strong><br>
* Created: <strong>12 Oct 2019</strong><br>
*
* @author Kai S. K. Engelbart
* @author Maximilian K&auml;fer
* @since Envoy Client v0.1-alpha
*/
public class UserListRenderer extends JLabel implements ListCellRenderer<User> {
private static final long serialVersionUID = 0L;
/**
* {@inheritDoc}
*/
@Override
public Component getListCellRendererComponent(JList<? extends User> list, User value, int index, boolean isSelected, boolean cellHasFocus) {
if (isSelected) {
setBackground(list.getSelectionBackground());
setForeground(list.getSelectionForeground());
} else {
setBackground(list.getBackground());
setForeground(list.getForeground());
}
// Enable background rendering
setOpaque(true);
final String name = value.getName();
final UserStatus status = value.getStatus();
this.setPreferredSize(new Dimension(100, 35));
// Getting the UserNameColor of the current theme
String textColor = null;
textColor = Settings.getInstance().getCurrentTheme().getUserNameColor().toHex();
switch (status) {
case ONLINE:
setText(String
.format("<html><p style=\"color:#03fc20\"><b><small>%s</b></small><br><p style=\"color:%s\">%s</html>", status, textColor, name));
break;
case OFFLINE:
setText(String
.format("<html><p style=\"color:#fc0303\"><b><small>%s</b></small><br><p style=\"color:%s\">%s</html>", status, textColor, name));
break;
}
return this;
}
}

View File

@ -1,14 +0,0 @@
/**
* This package contains all Envoy-specific renderers for lists that store an
* arbitrary number of JComponents.<br>
* <br>
* Project: <strong>envoy-client</strong><br>
* File: <strong>package-info.java</strong><br>
* Created: <strong>14 Mar 2020</strong><br>
*
* @author Leon Hofmeister
* @author Kai S. K. Engelbart
* @author Maximilian K&auml;fer
* @since Envoy Client v0.1-beta
*/
package envoy.client.ui.renderer;

View File

@ -0,0 +1,57 @@
package envoy.client.ui.settings;
import java.util.List;
import javafx.scene.control.ComboBox;
import javafx.scene.layout.VBox;
import envoy.client.data.Settings;
import envoy.client.data.SettingsItem;
import envoy.client.event.ThemeChangeEvent;
import envoy.data.User.UserStatus;
import envoy.event.EventBus;
/**
* Project: <strong>envoy-client</strong><br>
* File: <strong>GeneralSettingsPane.java</strong><br>
* Created: <strong>18.04.2020</strong><br>
*
* @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta
*/
public class GeneralSettingsPane extends SettingsPane {
private static final Settings settings = Settings.getInstance();
/**
* @since Envoy Client v0.1-beta
*/
public GeneralSettingsPane() {
super("General");
final var vbox = new VBox();
// TODO: Support other value types
List.of("onCloseMode", "enterToSend")
.stream()
.map(settings.getItems()::get)
.map(i -> new SettingsCheckbox((SettingsItem<Boolean>) i))
.forEach(vbox.getChildren()::add);
final var combobox = new ComboBox<String>();
combobox.getItems().add("dark");
combobox.getItems().add("light");
combobox.setValue(settings.getCurrentTheme());
combobox.setOnAction(
e -> { settings.setCurrentTheme(combobox.getValue()); EventBus.getInstance().dispatch(new ThemeChangeEvent(combobox.getValue())); });
vbox.getChildren().add(combobox);
final var statusComboBox = new ComboBox<UserStatus>();
statusComboBox.getItems().setAll(UserStatus.values());
statusComboBox.setValue(UserStatus.ONLINE);
// TODO add action when value is changed
statusComboBox.setOnAction(e -> {});
vbox.getChildren().add(statusComboBox);
getChildren().add(vbox);
}
}

View File

@ -1,89 +0,0 @@
package envoy.client.ui.settings;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JComponent;
import javax.swing.JTextPane;
import envoy.client.data.Settings;
import envoy.client.data.SettingsItem;
import envoy.client.ui.Theme;
import envoy.util.EnvoyLog;
/**
* Displays GUI components that allow general settings regarding the client.<br>
* <br>
* Project: <strong>envoy-client</strong><br>
* File: <strong>GeneralSettingsPanel.java</strong><br>
* Created: <strong>21 Dec 2019</strong><br>
*
* @author Maximilian K&auml;fer
* @since Envoy Client v0.3-alpha
*/
public class GeneralSettingsPanel extends SettingsPanel {
private Theme theme;
private static final String[] items = { "onCloseMode", "enterToSend" };
private static final Logger logger = EnvoyLog.getLogger(GeneralSettingsPanel.class);
private static final long serialVersionUID = 0L;
/**
* This is the constructor for the General class. Here the user can set general
* settings for the client.
*
* @param parent the {@link SettingsScreen} as a part of which this
* {@link SettingsPanel} is displayed
* @since Envoy Client v0.3-alpha
*/
public GeneralSettingsPanel(SettingsScreen parent) {
super(parent);
theme = Settings.getInstance().getCurrentTheme();
setBackground(theme.getCellColor());
GridBagLayout gbl_general = new GridBagLayout();
gbl_general.columnWidths = new int[] { 1, 1 };
gbl_general.rowHeights = new int[] { 1, 1, 1 };
gbl_general.columnWeights = new double[] { 1.0, 0.1 };
gbl_general.rowWeights = new double[] { 0.02, 0.02, 1.0 };
setLayout(gbl_general);
for (int i = 0; i < items.length; i++)
try {
createSettingElement(i, Settings.getInstance().getItems().get(items[i]));
} catch (SecurityException | ReflectiveOperationException e) {
logger.log(Level.WARNING, "Could not create settings item", e);
}
}
private void createSettingElement(int gridy, SettingsItem<?> settingsItem) throws SecurityException, ReflectiveOperationException {
JTextPane descriptionText = new JTextPane();
JComponent settingComponent = settingsItem.getComponent();
GridBagConstraints gbc_toggleSwitch = new GridBagConstraints();
gbc_toggleSwitch.gridx = 1;
gbc_toggleSwitch.gridy = gridy;
add(settingComponent, gbc_toggleSwitch);
descriptionText.setText(settingsItem.getDescription());
descriptionText.setBackground(theme.getBackgroundColor());
descriptionText.setForeground(theme.getBackgroundColor().invert());
descriptionText.setEditable(false);
GridBagConstraints gbc_descriptionText = new GridBagConstraints();
gbc_descriptionText.fill = GridBagConstraints.BOTH;
gbc_descriptionText.gridx = 0;
gbc_descriptionText.gridy = gridy;
gbc_descriptionText.insets = new Insets(5, 5, 5, 5);
add(descriptionText, gbc_descriptionText);
}
}

View File

@ -1,228 +0,0 @@
package envoy.client.ui.settings;
import java.awt.*;
import java.util.function.Consumer;
import javax.swing.JDialog;
import javax.swing.JPanel;
import javax.swing.JTextPane;
import envoy.client.data.Settings;
import envoy.client.ui.Theme;
import envoy.client.ui.primary.PrimaryButton;
import envoy.client.ui.primary.PrimaryTextArea;
/**
* Displays window where you can choose a name for the new {@link Theme}.
* <br>
* Project: <strong>envoy-client</strong><br>
* File: <strong>NewThemeScreen.java</strong><br>
* Created: <strong>26 Dec 2019</strong><br>
*
* @author Maximilian K&auml;fer
* @since Envoy Client v0.3-alpha
*/
public class NewThemeScreen extends JDialog {
private final JPanel standardPanel = new JPanel();
private final JPanel secondaryPanel = new JPanel();
private JTextPane text = new JTextPane();
private PrimaryTextArea nameEnterTextArea = new PrimaryTextArea(4);
private PrimaryButton confirmButton = new PrimaryButton("Confirm");
private JTextPane errorText = new JTextPane();
private PrimaryButton otherName = new PrimaryButton("Other Name");
private PrimaryButton overwrite = new PrimaryButton("Overwrite");
private final Consumer<String> newThemeAction, modifyThemeAction;
private static final long serialVersionUID = 0L;
/**
* Creates a window, where you can choose a name for a new {@link Theme}. <br>
* There are two versions of this Window. The first one is responsible for
* choosing the name, the second one appears, if the name already exists.
*
* @param parent the dialog is launched with its location relative to
* this {@link SettingsScreen}
* @param newThemeAction is executed when a new theme name is entered
* @param modifyThemeAction is executed when an existing theme name is entered
* and confirmed
* @since Envoy Client v0.3-alpha
*/
public NewThemeScreen(SettingsScreen parent, Consumer<String> newThemeAction, Consumer<String> modifyThemeAction) {
this.newThemeAction = newThemeAction;
this.modifyThemeAction = modifyThemeAction;
setLocationRelativeTo(parent);
setTitle("New Theme");
setModal(true);
setDimensions(true);
setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
Theme theme = Settings.getInstance().getCurrentTheme();
getContentPane().setLayout(new BorderLayout());
standardPanel.setBackground(theme.getBackgroundColor());
secondaryPanel.setBackground(theme.getBackgroundColor());
loadStandardContent(theme);
}
private void setDimensions(boolean isStandard) {
Dimension size = isStandard ? new Dimension(300, 170) : new Dimension(300, 225);
setPreferredSize(size);
setMinimumSize(size);
setMaximumSize(size);
}
private void loadStandardContent(Theme theme) {
getContentPane().removeAll();
// ContentPane
GridBagLayout gbl_contentPanel = new GridBagLayout();
gbl_contentPanel.columnWidths = new int[] { 1, 1 };
gbl_contentPanel.rowHeights = new int[] { 1, 1, 1 };
gbl_contentPanel.columnWeights = new double[] { 1, 1 };
gbl_contentPanel.rowWeights = new double[] { 1, 1, 1 };
getContentPane().add(standardPanel, BorderLayout.CENTER);
standardPanel.setLayout(gbl_contentPanel);
// text.setFont(new Font());
text.setText("Please enter a name for the new Theme");
text.setAlignmentX(CENTER_ALIGNMENT);
text.setBackground(theme.getCellColor());
text.setForeground(theme.getUserNameColor());
text.setEditable(false);
GridBagConstraints gbc_text = new GridBagConstraints();
gbc_text.fill = GridBagConstraints.HORIZONTAL;
gbc_text.gridx = 0;
gbc_text.gridy = 0;
gbc_text.gridwidth = 2;
gbc_text.insets = new Insets(5, 5, 5, 5);
standardPanel.add(text, gbc_text);
nameEnterTextArea.setBackground(theme.getCellColor());
nameEnterTextArea.setForeground(theme.getTypingMessageColor());
nameEnterTextArea.setText("");
nameEnterTextArea.setEditable(true);
GridBagConstraints gbc_input = new GridBagConstraints();
gbc_input.fill = GridBagConstraints.HORIZONTAL;
gbc_input.gridx = 0;
gbc_input.gridy = 1;
gbc_input.gridwidth = 2;
gbc_input.insets = new Insets(5, 5, 5, 5);
standardPanel.add(nameEnterTextArea, gbc_input);
confirmButton.setBackground(theme.getInteractableBackgroundColor());
confirmButton.setForeground(theme.getInteractableForegroundColor());
GridBagConstraints gbc_confirmButton = new GridBagConstraints();
gbc_confirmButton.gridx = 0;
gbc_confirmButton.gridy = 2;
gbc_confirmButton.gridwidth = 2;
gbc_confirmButton.insets = new Insets(5, 5, 5, 5);
standardPanel.add(confirmButton, gbc_confirmButton);
confirmButton.addActionListener((evt) -> {
if (!nameEnterTextArea.getText().isEmpty()) if (Settings.getInstance().getThemes().containsKey(nameEnterTextArea.getText())) {
// load other panel
setDimensions(false);
loadSecondaryPage(theme);
} else {
newThemeAction.accept(nameEnterTextArea.getText());
dispose();
}
});
}
private void loadSecondaryPage(Theme theme) {
// ContentPane
getContentPane().removeAll();
GridBagLayout gbl_secondaryPanel = new GridBagLayout();
gbl_secondaryPanel.columnWidths = new int[] { 1, 1 };
gbl_secondaryPanel.rowHeights = new int[] { 1, 1, 1, 1 };
gbl_secondaryPanel.columnWeights = new double[] { 1, 1 };
gbl_secondaryPanel.rowWeights = new double[] { 1, 1, 1, 1 };
getContentPane().add(secondaryPanel, BorderLayout.CENTER);
secondaryPanel.setLayout(gbl_secondaryPanel);
// text.setFont(new Font());
text.setText("Please enter a name for the new Theme");
text.setAlignmentX(CENTER_ALIGNMENT);
text.setBackground(theme.getCellColor());
text.setForeground(theme.getUserNameColor());
text.setEditable(false);
GridBagConstraints gbc_text = new GridBagConstraints();
gbc_text.fill = GridBagConstraints.HORIZONTAL;
gbc_text.gridx = 0;
gbc_text.gridy = 0;
gbc_text.gridwidth = 2;
gbc_text.insets = new Insets(5, 5, 5, 5);
secondaryPanel.add(text, gbc_text);
nameEnterTextArea.setBackground(theme.getCellColor());
nameEnterTextArea.setForeground(theme.getTypingMessageColor());
nameEnterTextArea.setEditable(false);
GridBagConstraints gbc_input = new GridBagConstraints();
gbc_input.fill = GridBagConstraints.HORIZONTAL;
gbc_input.gridx = 0;
gbc_input.gridy = 1;
gbc_input.gridwidth = 2;
gbc_input.insets = new Insets(5, 5, 5, 5);
secondaryPanel.add(nameEnterTextArea, gbc_input);
errorText.setText("The name does already exist. Choose another one or overwrite the old theme.");
errorText.setAlignmentX(CENTER_ALIGNMENT);
errorText.setBackground(theme.getCellColor());
errorText.setForeground(theme.getUserNameColor());
errorText.setEditable(false);
GridBagConstraints gbc_errorText = new GridBagConstraints();
gbc_errorText.fill = GridBagConstraints.HORIZONTAL;
gbc_errorText.gridx = 0;
gbc_errorText.gridy = 2;
gbc_errorText.gridwidth = 2;
gbc_errorText.insets = new Insets(5, 5, 5, 5);
secondaryPanel.add(errorText, gbc_errorText);
otherName.setBackground(theme.getInteractableBackgroundColor());
otherName.setForeground(theme.getInteractableForegroundColor());
GridBagConstraints gbc_otherName = new GridBagConstraints();
gbc_otherName.gridx = 0;
gbc_otherName.gridy = 3;
gbc_otherName.insets = new Insets(5, 5, 5, 5);
secondaryPanel.add(otherName, gbc_otherName);
overwrite.setBackground(theme.getInteractableBackgroundColor());
overwrite.setForeground(theme.getInteractableForegroundColor());
GridBagConstraints gbc_overwrite = new GridBagConstraints();
gbc_overwrite.gridx = 1;
gbc_overwrite.gridy = 3;
gbc_overwrite.insets = new Insets(5, 5, 5, 5);
secondaryPanel.add(overwrite, gbc_overwrite);
otherName.addActionListener((evt) -> { setDimensions(true); loadStandardContent(theme); });
overwrite.addActionListener((evt) -> { modifyThemeAction.accept(nameEnterTextArea.getText()); dispose(); });
}
}

View File

@ -0,0 +1,32 @@
package envoy.client.ui.settings;
import javafx.event.ActionEvent;
import javafx.scene.control.CheckBox;
import envoy.client.data.SettingsItem;
/**
* Project: <strong>envoy-client</strong><br>
* File: <strong>SettingsToggleButton.java</strong><br>
* Created: <strong>18.04.2020</strong><br>
*
* @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta
*/
public final class SettingsCheckbox extends CheckBox {
/**
* Creates an instance of {@link SettingsCheckbox}.
*
* @param settingsItem the {@link SettingsItem} whose values could be adapted
* @since Envoy Client v0.1-beta
*/
public SettingsCheckbox(SettingsItem<Boolean> settingsItem) {
super(settingsItem.getUserFriendlyName());
setSelected(settingsItem.get());
// "Schau, es hat sich behindert" - Kai, 2020
addEventHandler(ActionEvent.ACTION, e -> settingsItem.set(isSelected()));
}
}

View File

@ -0,0 +1,24 @@
package envoy.client.ui.settings;
import javafx.scene.layout.Pane;
/**
* Project: <strong>envoy-client</strong><br>
* File: <strong>SettingsPane.java</strong><br>
* Created: <strong>18.04.2020</strong><br>
*
* @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta
*/
public abstract class SettingsPane extends Pane {
protected String title;
protected SettingsPane(String title) { this.title = title; }
/**
* @return the title of this settings pane
* @since Envoy Client v0.1-beta
*/
public String getTitle() { return title; }
}

View File

@ -1,30 +0,0 @@
package envoy.client.ui.settings;
import javax.swing.JPanel;
/**
* Serves as an interface between {@link SettingsScreen} and different
* {@link JPanel}s with actual settings that are defined as sub classes of this
* class.<br>
* <br>
* Project: <strong>envoy-client</strong><br>
* File: <strong>SettingsPanel.java</strong><br>
* Created: <strong>20 Dec 2019</strong><br>
*
* @author Kai S. K. Engelbart
* @since Envoy Client v0.2-alpha
*/
public abstract class SettingsPanel extends JPanel {
protected final SettingsScreen parent;
private static final long serialVersionUID = 0L;
/**
* Initializes a {@link SettingsPanel}.
*
* @param parent the {@link SettingsScreen} as a part of which this
* {@link SettingsPanel} is displayed
*/
public SettingsPanel(SettingsScreen parent) { this.parent = parent; }
}

View File

@ -1,172 +0,0 @@
package envoy.client.ui.settings;
import java.awt.*;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.*;
import envoy.client.data.Settings;
import envoy.client.event.ThemeChangeEvent;
import envoy.client.ui.Theme;
import envoy.client.ui.primary.PrimaryButton;
import envoy.event.EventBus;
import envoy.util.EnvoyLog;
/**
* This class provides the GUI to change the user specific settings.<br>
* <br>
* Project: <strong>envoy-client</strong><br>
* File: <strong>SettingsScreen.java</strong><br>
* Created: <strong>31 Oct 2019</strong><br>
*
* @author Leon Hofmeister
* @author Maximilian K&auml;fer
* @author Kai S. K. Engelbart
* @since Envoy Client v0.2-alpha
*/
public class SettingsScreen extends JDialog {
private static final long serialVersionUID = 0L;
private final JPanel contentPanel = new JPanel();
// Settings panel list
private final DefaultListModel<String> optionsListModel = new DefaultListModel<>();
private final JList<String> options = new JList<>(optionsListModel);
// OK and cancel buttons
private final JPanel buttonPane = new JPanel();
private final PrimaryButton cancelButton = new PrimaryButton("Cancel");
private final Insets insets = new Insets(5, 5, 5, 5);
private SettingsPanel settingsPanel;
private static final Logger logger = EnvoyLog.getLogger(SettingsScreen.class);
/**
* Initializes the settings screen.
*
* @since Envoy Client v0.1-alpha
*/
public SettingsScreen() {
// Initialize settings pages
Map<String, Class<? extends SettingsPanel>> panels = new HashMap<>();
panels.put("General", GeneralSettingsPanel.class);
panels.put("Color Themes", ThemeCustomizationPanel.class);
setBounds(10, 10, 450, 650);
getContentPane().setLayout(new BorderLayout());
{
// ContentPane
GridBagLayout gbl_contentPanel = new GridBagLayout();
gbl_contentPanel.columnWidths = new int[] { 1, 1 };
gbl_contentPanel.rowHeights = new int[] { 1 };
gbl_contentPanel.columnWeights = new double[] { 0.05, 1.0 };
gbl_contentPanel.rowWeights = new double[] { 1.0 };
getContentPane().add(contentPanel, BorderLayout.CENTER);
contentPanel.setLayout(gbl_contentPanel);
// Constraints for the settings panel
GridBagConstraints gbc_panel = new GridBagConstraints();
gbc_panel.fill = GridBagConstraints.BOTH;
gbc_panel.gridx = 1;
gbc_panel.gridy = 0;
gbc_panel.anchor = GridBagConstraints.PAGE_START;
gbc_panel.insets = insets;
options.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
options.addListSelectionListener((listSelectionEvent) -> {
if (!listSelectionEvent.getValueIsAdjusting()) {
// Get selected settings panel
final String option = options.getSelectedValue();
logger.log(Level.FINEST, "Selected settings panel: " + option);
// Remove previous settings panel
if (settingsPanel != null) contentPanel.remove(settingsPanel);
try {
settingsPanel = panels.get(option).getDeclaredConstructor(getClass()).newInstance(this);
// Add selected settings panel
contentPanel.add(settingsPanel, gbc_panel);
revalidate();
repaint();
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException
| NoSuchMethodException | SecurityException e) {
logger.log(Level.SEVERE, "Failed to invoke constructor of SettingsPanel " + option, e);
}
}
});
options.setFont(new Font("Arial", Font.PLAIN, 14));
GridBagConstraints gbc_optionsList = new GridBagConstraints();
gbc_optionsList.fill = GridBagConstraints.BOTH;
gbc_optionsList.gridx = 0;
gbc_optionsList.gridy = 0;
gbc_optionsList.anchor = GridBagConstraints.PAGE_START;
gbc_optionsList.insets = insets;
panels.keySet().forEach(name -> optionsListModel.addElement(name));
contentPanel.add(options, gbc_optionsList);
// ButtonPane
GridBagLayout gbl_buttonPane = new GridBagLayout();
gbl_buttonPane.columnWidths = new int[] { 1, 1 };
gbl_buttonPane.rowHeights = new int[] { 25 };
gbl_buttonPane.columnWeights = new double[] { 1.0, 1.0 };
gbl_buttonPane.rowWeights = new double[] { 0.0 };
getContentPane().add(buttonPane, BorderLayout.SOUTH);
buttonPane.setLayout(gbl_buttonPane);
{
cancelButton.setActionCommand("Cancel");
cancelButton.setBorderPainted(false);
GridBagConstraints gbc_cancelButton = new GridBagConstraints();
gbc_cancelButton.anchor = GridBagConstraints.NORTHWEST;
gbc_cancelButton.insets = insets;
gbc_cancelButton.gridx = 0;
gbc_cancelButton.gridy = 0;
buttonPane.add(cancelButton, gbc_cancelButton);
cancelButton.addActionListener((evt) -> { dispose(); });
}
}
// Apply current theme
applyTheme(Settings.getInstance().getCurrentTheme());
// Respond to theme changes
EventBus.getInstance().register(ThemeChangeEvent.class, evt -> applyTheme(evt.get()));
setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
setModal(true);
}
private void applyTheme(Theme theme) {
// JDialog
setBackground(theme.getBackgroundColor());
// contentPanel
contentPanel.setBackground(theme.getBackgroundColor());
// buttonPane
buttonPane.setBackground(theme.getCellColor());
// cancelButton
cancelButton.setBackground(theme.getInteractableBackgroundColor());
cancelButton.setForeground(theme.getInteractableForegroundColor());
// options
options.setSelectionForeground(theme.getUserNameColor());
options.setSelectionBackground(theme.getSelectionColor());
options.setForeground(theme.getUserNameColor());
options.setBackground(theme.getCellColor());
}
}

View File

@ -1,246 +0,0 @@
package envoy.client.ui.settings;
import java.awt.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.*;
import envoy.client.data.Settings;
import envoy.client.event.ThemeChangeEvent;
import envoy.client.ui.Color;
import envoy.client.ui.Theme;
import envoy.client.ui.primary.PrimaryButton;
import envoy.event.EventBus;
import envoy.util.EnvoyLog;
/**
* Displays GUI components that allow changing the current {@Theme} and creating
* new ones.<br>
* <br>
* Project: <strong>envoy-client</strong><br>
* File: <strong>ThemeCustomizationPanel.java</strong><br>
* Created: <strong>20 Dec 2019</strong><br>
*
* @author Kai S. K. Engelbart
* @author Maximilian K&auml;fer
* @since Envoy Client v0.2-alpha
*/
public class ThemeCustomizationPanel extends SettingsPanel {
private JPanel colorsPanel = new JPanel();
private DefaultComboBoxModel<String> themesModel;
private JComboBox<String> themes;
private Theme temporaryTheme;
private PrimaryButton createThemeButton = new PrimaryButton("Create Theme");
private boolean themeChanged;
private final Insets insets = new Insets(5, 5, 5, 5);
private static final Logger logger = EnvoyLog.getLogger(ThemeCustomizationPanel.class);
private static final long serialVersionUID = 0L;
/**
* Initializes a {@link ThemeCustomizationPanel} that enables the user to change
* the current {@link Theme} and create new themes as part of the
* {@link SettingsScreen}.
*
* @param parent the {@link SettingsScreen} as a part of which this
* {@link SettingsPanel} is displayed
* @since Envoy Client v0.2-alpha
*/
public ThemeCustomizationPanel(SettingsScreen parent) {
super(parent);
temporaryTheme = new Theme("temporaryTheme", Settings.getInstance().getCurrentTheme());
var themeNames = Settings.getInstance().getThemes().keySet().toArray(new String[0]);
String currentThemeName = Settings.getInstance().getCurrentThemeName();
for (int i = 0; i < themeNames.length; i++)
if (currentThemeName.equals(themeNames[i])) {
themeNames[i] = themeNames[0];
themeNames[0] = currentThemeName;
break;
}
themesModel = new DefaultComboBoxModel<>(themeNames);
themes = new JComboBox<>(themesModel);
GridBagLayout gbl_themeLayout = new GridBagLayout();
gbl_themeLayout.columnWidths = new int[] { 1, 1 };
gbl_themeLayout.rowHeights = new int[] { 1, 1, 1 };
gbl_themeLayout.columnWeights = new double[] { 1.0, 1.0 };
gbl_themeLayout.rowWeights = new double[] { 0.01, 1.0, 0.01 };
setLayout(gbl_themeLayout);
themes.setSelectedItem(Settings.getInstance().getCurrentTheme());
GridBagConstraints gbc_themes = new GridBagConstraints();
gbc_themes.fill = GridBagConstraints.HORIZONTAL;
gbc_themes.gridwidth = 2;
gbc_themes.gridx = 0;
gbc_themes.gridy = 0;
gbc_themes.anchor = GridBagConstraints.NORTHWEST;
gbc_themes.insets = new Insets(10, 10, 20, 10);
add(themes, gbc_themes);
GridBagLayout gbl_colorCustomizations = new GridBagLayout();
gbl_colorCustomizations.columnWidths = new int[] { 1, 1 };
gbl_colorCustomizations.rowHeights = new int[] { 1, 1, 1, 1, 1, 1, 1, 1, 1 };
gbl_colorCustomizations.columnWeights = new double[] { 1, 1 };
gbl_colorCustomizations.rowWeights = new double[] { 1, 1, 1, 1, 1, 1, 1, 1 };
colorsPanel.setLayout(gbl_colorCustomizations);
Theme theme = Settings.getInstance().getCurrentTheme();
buildCustomizeElements(theme);
GridBagConstraints gbc_colorsPanel = new GridBagConstraints();
gbc_colorsPanel.fill = GridBagConstraints.HORIZONTAL;
gbc_colorsPanel.gridx = 0;
gbc_colorsPanel.gridy = 1;
gbc_colorsPanel.gridwidth = 2;
gbc_colorsPanel.anchor = GridBagConstraints.NORTHWEST;
gbc_colorsPanel.insets = insets;
add(colorsPanel, gbc_colorsPanel);
createThemeButton.addActionListener((evt) -> {
if (themeChanged) {
new NewThemeScreen(parent, name -> {
// Create new theme
logger.log(Level.FINEST, name);
Settings.getInstance().addNewThemeToMap(new Theme(name, temporaryTheme));
// Add new theme name to combo box
themesModel.addElement(name);
// Select new theme name
themes.setSelectedIndex(themesModel.getSize() - 1);
}, name -> {
// Modify theme
Settings.getInstance().getThemes().replace(name, new Theme(name, temporaryTheme));
if (themes.getSelectedItem().equals(name))
EventBus.getInstance().dispatch(new ThemeChangeEvent(Settings.getInstance().getTheme(name)));
else themes.setSelectedItem(name);
}).setVisible(true);
themeChanged = false;
}
});
GridBagConstraints gbc_createThemeButton = new GridBagConstraints();
gbc_createThemeButton.fill = GridBagConstraints.HORIZONTAL;
gbc_createThemeButton.gridx = 0;
gbc_createThemeButton.gridy = 2;
gbc_createThemeButton.anchor = GridBagConstraints.CENTER;
gbc_createThemeButton.insets = insets;
add(createThemeButton, gbc_createThemeButton);
colorsPanel.setBackground(theme.getCellColor());
// Apply theme upon selection
themes.addItemListener(e -> {
String selectedValue = (String) themes.getSelectedItem();
logger.log(Level.FINEST, "Selected theme: " + selectedValue);
final Theme currentTheme = Settings.getInstance().getTheme(selectedValue);
Settings.getInstance().setCurrentTheme(selectedValue);
EventBus.getInstance().dispatch(new ThemeChangeEvent(currentTheme));
});
// Apply current theme
applyTheme(theme);
// Respond to theme changes
EventBus.getInstance()
.register(ThemeChangeEvent.class,
evt -> {
final Theme currentTheme = evt.get();
temporaryTheme = new Theme("temporaryTheme", currentTheme);
applyTheme(currentTheme);
});
}
private void applyTheme(Theme theme) {
// themeContent
setForeground(theme.getUserNameColor());
setBackground(theme.getCellColor());
// createThemeButton
createThemeButton.setForeground(theme.getInteractableForegroundColor());
createThemeButton.setBackground(theme.getInteractableBackgroundColor());
// themes
themes.setBackground(theme.getInteractableBackgroundColor());
themes.setForeground(theme.getInteractableForegroundColor());
colorsPanel.setBackground(theme.getCellColor());
// Color panel
updateColorVariables(theme);
revalidate();
repaint();
}
private void updateColorVariables(Theme theme) {
colorsPanel.removeAll();
buildCustomizeElements(theme);
}
private void buildCustomizeElements(Theme theme) {
buildCustomizeElement(theme, theme.getBackgroundColor(), "Background", "backgroundColor", 1);
buildCustomizeElement(theme, theme.getCellColor(), "Cells", "cellColor", 2);
buildCustomizeElement(theme, theme.getInteractableForegroundColor(), "Interactable Foreground", "interactableForegroundColor", 3);
buildCustomizeElement(theme, theme.getInteractableBackgroundColor(), "Interactable Background", "interactableBackgroundColor", 4);
buildCustomizeElement(theme, theme.getTextColor(), "Text Color", "textColor", 5);
buildCustomizeElement(theme, theme.getDateColor(), "Date Chat", "dateColorChat", 6);
buildCustomizeElement(theme, theme.getSelectionColor(), "Selection", "selectionColor", 7);
buildCustomizeElement(theme, theme.getTypingMessageColor(), "Typing Message", "typingMessageColor", 8);
buildCustomizeElement(theme, theme.getUserNameColor(), "User Names", "userNameColor", 9);
}
private void buildCustomizeElement(Theme theme, Color color, String name, String colorName, int gridy) {
JButton button = new JButton();
JTextPane textPane = new JTextPane();
textPane.setFont(new Font("Arial", Font.PLAIN, 14));
textPane.setBackground(theme.getBackgroundColor());
textPane.setForeground(theme.getBackgroundColor().invert());
textPane.setText(name);
textPane.setEditable(false);
button.setBackground(color);
button.setPreferredSize(new Dimension(25, 25));
button.addActionListener((evt) -> {
java.awt.Color c = JColorChooser.showDialog(null, "Choose a color", color);
if (c != null) {
Color newColor = new Color(c);
if (!color.equals(newColor)) {
logger.log(Level.FINEST, "New Color: " + newColor);
temporaryTheme.setColor(colorName, newColor);
themeChanged = true;
}
button.setBackground(newColor);
}
});
GridBagConstraints gbc_textPane = new GridBagConstraints();
gbc_textPane.fill = GridBagConstraints.BOTH;
gbc_textPane.gridx = 0;
gbc_textPane.gridy = gridy;
gbc_textPane.anchor = GridBagConstraints.CENTER;
gbc_textPane.insets = insets;
colorsPanel.add(textPane, gbc_textPane);
GridBagConstraints gbc_button = new GridBagConstraints();
gbc_button.fill = GridBagConstraints.BOTH;
gbc_button.gridx = 1;
gbc_button.gridy = gridy;
gbc_button.anchor = GridBagConstraints.CENTER;
gbc_button.insets = insets;
colorsPanel.add(button, gbc_button);
}
}

View File

@ -1,9 +1,14 @@
/**
* This package contains user interface classes related to the settings screen.
* This package contains classes used for representing the settings
* visually.<br>
* <br>
* Project: <strong>envoy-client</strong><br>
* File: <strong>package-info.java</strong><br>
* Created: <strong>19 Apr 2020</strong><br>
*
* @author Kai S. K. Engelbart
* @author Leon Hofmeister
* @author Kai S. K. Engelbart
* @author Maximilian K&auml;fer
* @since Envoy Client v0.2-alpha
* @since Envoy Client v0.1-beta
*/
package envoy.client.ui.settings;

View File

@ -12,6 +12,12 @@ module envoy {
requires transitive envoy.common;
requires transitive java.desktop;
requires transitive java.logging;
requires transitive java.naming;
requires transitive java.prefs;
requires javafx.controls;
requires javafx.fxml;
requires javafx.base;
requires javafx.graphics;
opens envoy.client.ui to javafx.graphics, javafx.fxml;
opens envoy.client.ui.controller to javafx.graphics, javafx.fxml;
}

View File

@ -1,3 +1,3 @@
server=http://kske.feste-ip.net
port=43315
server=localhost
port=8080
localDB=.\\localDB

View File

@ -0,0 +1,3 @@
.button {
-fx-background-radius: 5em;
}

View File

@ -0,0 +1,4 @@
.button{
-fx-background-color: rgb(105,0,153);
-fx-text-fill: white;
}

View File

@ -0,0 +1,3 @@
.button{
-fx-background-color: snow;
}

View File

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.ListView?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>
<GridPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="400.0" minWidth="350.0" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="envoy.client.ui.controller.ChatScene">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" maxWidth="1.7976931348623157E308" minWidth="10.0" percentWidth="20.0" prefWidth="161.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="1.7976931348623157E308" minWidth="10.0" percentWidth="65.0" prefWidth="357.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="1.7976931348623157E308" minWidth="6.285736083984375" percentWidth="15.0" prefWidth="48.000030517578125" />
</columnConstraints>
<rowConstraints>
<RowConstraints maxHeight="1.7976931348623157E308" minHeight="10.0" percentHeight="10.0" prefHeight="70.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="1.7976931348623157E308" minHeight="10.0" percentHeight="70.0" prefHeight="326.2857404436384" vgrow="SOMETIMES" />
<RowConstraints maxHeight="1.7976931348623157E308" minHeight="10.0" percentHeight="5.0" prefHeight="50.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="1.7976931348623157E308" minHeight="10.0" percentHeight="15.0" prefHeight="100.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<ListView fx:id="userList" onMouseClicked="#userListClicked" prefHeight="211.0" prefWidth="300.0" GridPane.rowIndex="1" GridPane.rowSpan="2147483647" />
<Label fx:id="contactLabel" prefHeight="16.0" prefWidth="250.0" text="Select a contact to chat with" GridPane.columnSpan="2" />
<Button fx:id="settingsButton" mnemonicParsing="true" onAction="#settingsButtonClicked" text="_Settings" GridPane.columnIndex="2" GridPane.halignment="CENTER" GridPane.valignment="CENTER" />
<ListView fx:id="messageList" prefHeight="257.0" prefWidth="155.0" GridPane.columnIndex="1" GridPane.columnSpan="2" GridPane.rowIndex="1" GridPane.rowSpan="2" />
<Button fx:id="postButton" defaultButton="true" disable="true" mnemonicParsing="true" onAction="#postButtonClicked" prefHeight="10.0" prefWidth="65.0" text="_Post" GridPane.columnIndex="2" GridPane.halignment="CENTER" GridPane.rowIndex="3" GridPane.valignment="CENTER" />
<TextArea fx:id="messageTextArea" disable="true" onInputMethodTextChanged="#messageTextUpdated" onKeyPressed="#checkKeyCombination" onKeyTyped="#messageTextUpdated" prefHeight="200.0" prefWidth="200.0" wrapText="true" GridPane.columnIndex="1" GridPane.rowIndex="3" />
<Button mnemonicParsing="true" onAction="#addContactButtonClicked" text="_Add Contacts" GridPane.halignment="CENTER" GridPane.rowIndex="3" GridPane.valignment="CENTER">
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
<GridPane.margin>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</GridPane.margin>
</Button>
<Label fx:id="remainingChars" disable="true" ellipsisString="" maxHeight="30.0" maxWidth="180.0" prefHeight="30.0" prefWidth="180.0" text="remaining chars: 0/x" textFill="LIME" textOverrun="LEADING_WORD_ELLIPSIS" visible="false" GridPane.columnIndex="1" GridPane.rowIndex="2">
<GridPane.margin>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</GridPane.margin>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
</Label>
</children>
</GridPane>

View File

@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ListView?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.control.Tooltip?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.layout.VBox?>
<VBox maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="envoy.client.ui.controller.ContactSearchScene">
<children>
<HBox alignment="CENTER" prefHeight="100.0" prefWidth="200.0">
<children>
<TextField fx:id="searchBar" onInputMethodTextChanged="#checkClearButton" onKeyTyped="#checkClearButton" prefColumnCount="22" promptText="Enter username to search for">
<HBox.margin>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</HBox.margin>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
<tooltip>
<Tooltip text="Enter a name. If an account by that name exists, it will be displayed below." wrapText="true" />
</tooltip>
</TextField>
<Button fx:id="clearButton" disable="true" mnemonicParsing="true" onAction="#clear" text="Clea_r">
<tooltip>
<Tooltip autoHide="true" text="Clears the text to the left and the elements below" wrapText="true" />
</tooltip>
</Button>
<Pane disable="true" maxWidth="20.0" prefWidth="20.0" visible="false">
<HBox.margin>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</HBox.margin>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
</Pane>
<Button fx:id="searchButton" disable="true" defaultButton="true" mnemonicParsing="true" onAction="#suggestContacts" prefHeight="26.0" prefWidth="71.0" text="_Search" textOverrun="LEADING_WORD_ELLIPSIS">
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
<HBox.margin>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</HBox.margin>
<tooltip>
<Tooltip autoHide="true" text="Search for accounts with the name entered to the left" wrapText="true" />
</tooltip>
</Button>
</children>
</HBox>
<ListView fx:id="contactList" onMouseClicked="#contactListClicked" prefHeight="314.0" prefWidth="600.0" />
<Button fx:id="backButton" cancelButton="true" mnemonicParsing="true" onAction="#backButtonClicked" text="_Back">
<VBox.margin>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</VBox.margin>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
<tooltip>
<Tooltip autoHide="true" text="Takes you back to the screen where you can chat with others" wrapText="true" />
</tooltip>
</Button>
</children>
</VBox>

View File

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ButtonBar?>
<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.PasswordField?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?>
<VBox prefHeight="206.0" prefWidth="440.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="envoy.client.ui.controller.LoginScene">
<children>
<Label text="User Login">
<font>
<Font size="26.0" />
</font>
</Label>
<GridPane>
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" percentWidth="40.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Label text="User Name:" />
<Label text="Password" GridPane.rowIndex="1" />
<Label fx:id="repeatPasswordLabel" text="Repeat Password:" visible="false" GridPane.rowIndex="2" />
<TextField fx:id="userTextField" GridPane.columnIndex="1" />
<PasswordField fx:id="passwordField" GridPane.columnIndex="1" GridPane.rowIndex="1" />
<PasswordField fx:id="repeatPasswordField" promptText="Repeat the chosen password" visible="false" GridPane.columnIndex="1" GridPane.rowIndex="2" />
</children>
</GridPane>
<CheckBox fx:id="registerCheckBox" mnemonicParsing="true" onAction="#registerCheckboxChanged" prefHeight="17.0" prefWidth="181.0" text="_Register" />
<Label fx:id="connectionLabel" />
<ButtonBar prefHeight="40.0" prefWidth="200.0">
<buttons>
<Button mnemonicParsing="false" onAction="#abortLogin" text="Close" />
<Button mnemonicParsing="false" onAction="#offlineModeButtonPressed" text="Offline mode" />
<Button defaultButton="true" mnemonicParsing="false" onAction="#loginButtonPressed" text="Login" />
</buttons>
</ButtonBar>
</children>
</VBox>

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ListView?>
<?import javafx.scene.control.TitledPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<VBox alignment="TOP_RIGHT" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="envoy.client.ui.controller.SettingsScene">
<children>
<HBox prefHeight="100.0" prefWidth="200.0">
<children>
<ListView fx:id="settingsList" onMouseClicked="#settingsListClicked" prefHeight="200.0" prefWidth="200.0" />
<TitledPane fx:id="titledPane" collapsible="false" prefHeight="200.0" prefWidth="200.0" />
</children>
</HBox>
<Button defaultButton="true" mnemonicParsing="true" onMouseClicked="#backButtonClicked" text="_Back">
<opaqueInsets>
<Insets />
</opaqueInsets>
</Button>
</children>
</VBox>

View File

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB