diff --git a/.gitignore b/.gitignore
index a6f89c2..e01c619 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,4 @@
-/target/
\ No newline at end of file
+/target/
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs
index 7d167b0..5e625c6 100644
--- a/.settings/org.eclipse.jdt.core.prefs
+++ b/.settings/org.eclipse.jdt.core.prefs
@@ -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
diff --git a/.settings/org.eclipse.jdt.ui.prefs b/.settings/org.eclipse.jdt.ui.prefs
index 62985b5..f6a7cc1 100644
--- a/.settings/org.eclipse.jdt.ui.prefs
+++ b/.settings/org.eclipse.jdt.ui.prefs
@@ -1,7 +1,9 @@
eclipse.preferences.version=1
+formatter_profile=_KSKE
+formatter_settings_version=18
org.eclipse.jdt.ui.ignorelowercasenames=true
-org.eclipse.jdt.ui.importorder=java;javax;org;com;
+org.eclipse.jdt.ui.importorder=java;javax;javafx;org;com;envoy;
org.eclipse.jdt.ui.javadoc=true
org.eclipse.jdt.ui.ondemandthreshold=4
org.eclipse.jdt.ui.staticondemandthreshold=2
-org.eclipse.jdt.ui.text.custom_code_templates=/**\n * @return the ${bare_field_name}\n * @since Envoy Client v0.1-beta\n *//**\n * @param ${param} the ${bare_field_name} to set\n * @since Envoy Client v0.1-beta\n *//**\n * @since Envoy Client v0.1-beta\n *//**\n * Project\: <strong>${project_name}</strong><br>\n * File\: <strong>${file_name}</strong><br>\n * Created\: <strong>${date}</strong><br>\n * \n * @author ${user}\n * @since Envoy Client v0.1-beta\n *//**\n * ${tags}\n * @since Envoy Client v0.1-beta\n *//**\n * @author ${user}\n *\n * ${tags}\n * @since Envoy Client v0.1-beta\n *//**\n * {@inheritDoc}\n *//**\n * ${tags}\n * ${see_to_target}\n * @since Envoy Client v0.1-beta\n */${filecomment}\n${package_declaration}\n\n${typecomment}\n${type_declaration}\n\n\n\n${exception_var}.printStackTrace();${body_statement}${body_statement}return ${field};${field} \= ${param};
+org.eclipse.jdt.ui.text.custom_code_templates=/**\n * @return the ${bare_field_name}\n * @since Envoy Client v0.1-beta\n *//**\n * @param ${param} the ${bare_field_name} to set\n * @since Envoy Client v0.1-beta\n *//**\n * ${tags}\n * @since Envoy Client v0.1-beta\n *//**\n * Project\: <strong>${project_name}</strong><br>\n * File\: <strong>${file_name}</strong><br>\n * Created\: <strong>${date}</strong><br>\n * \n * @author ${user}\n * @since Envoy Client v0.1-beta\n *//**\n * ${tags}\n * @since Envoy Client v0.1-beta\n *//**\n * @author ${user}\n *\n * ${tags}\n * @since Envoy Client v0.1-beta\n *//**\n * {@inheritDoc}\n *//**\n * ${tags}\n * ${see_to_target}\n * @since Envoy Client v0.1-beta\n */${filecomment}\n${package_declaration}\n\n${typecomment}\n${type_declaration}\n\n\n\n${exception_var}.printStackTrace();${body_statement}${body_statement}return ${field};${field} \= ${param};
diff --git a/.settings/org.hibernate.eclipse.console.prefs b/.settings/org.hibernate.eclipse.console.prefs
new file mode 100644
index 0000000..21fefff
--- /dev/null
+++ b/.settings/org.hibernate.eclipse.console.prefs
@@ -0,0 +1,3 @@
+default.configuration=
+eclipse.preferences.version=1
+hibernate3.enabled=false
diff --git a/pom.xml b/pom.xml
index 572b5ed..59bb365 100644
--- a/pom.xml
+++ b/pom.xml
@@ -28,7 +28,17 @@
com.github.informatik-ag-nglenvoy-common
- develop-SNAPSHOT
+ f~groups-SNAPSHOT
+
+
+ org.openjfx
+ javafx-controls
+ 11.0.2
+
+
+ org.openjfx
+ javafx-fxml
+ 11.0.2
diff --git a/src/main/java/envoy/client/Startup.java b/src/main/java/envoy/client/Startup.java
deleted file mode 100644
index 3e9180c..0000000
--- a/src/main/java/envoy/client/Startup.java
+++ /dev/null
@@ -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.
- *
- * Project: envoy-client
- * File: Startup.java
- * Created: 12 Oct 2019
- *
- * @author Leon Hofmeister
- * @author Maximilian Kä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 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);
- }
- }));
- }
-}
diff --git a/src/main/java/envoy/client/data/Chat.java b/src/main/java/envoy/client/data/Chat.java
index 162b256..a9148e2 100644
--- a/src/main/java/envoy/client/data/Chat.java
+++ b/src/main/java/envoy/client/data/Chat.java
@@ -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 messages = new ArrayList<>();
- private final User recipient;
- private final Model model = new Model<>();
+ private static final long serialVersionUID = 1L;
/**
* Provides the list of messages that the recipient receives.
@@ -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 getModel() { return model; }
+ public List 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; }
}
diff --git a/src/main/java/envoy/client/data/ClientConfig.java b/src/main/java/envoy/client/data/ClientConfig.java
index 63381a0..0b11e40 100644
--- a/src/main/java/envoy/client/data/ClientConfig.java
+++ b/src/main/java/envoy/client/data/ClientConfig.java
@@ -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); }
}
diff --git a/src/main/java/envoy/client/data/LocalDB.java b/src/main/java/envoy/client/data/LocalDB.java
index 0980289..a9b0665 100644
--- a/src/main/java/envoy/client/data/LocalDB.java
+++ b/src/main/java/envoy/client/data/LocalDB.java
@@ -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 users = new HashMap<>();
+ protected Map users = new HashMap<>();
protected List chats = new ArrayList<>();
protected IDGenerator idGenerator;
protected Cache messageCache = new Cache<>();
@@ -71,12 +71,12 @@ public abstract class LocalDB {
* user names as keys
* @since Envoy Client v0.2-alpha
*/
- public Map getUsers() { return users; }
+ public Map getUsers() { return users; }
/**
* @param users the users to set
*/
- public void setUsers(Map users) { this.users = users; }
+ public void setUsers(Map 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;
+ }
+ });
}
}
diff --git a/src/main/java/envoy/client/data/PersistentLocalDB.java b/src/main/java/envoy/client/data/PersistentLocalDB.java
index a4fe390..9548516 100644
--- a/src/main/java/envoy/client/data/PersistentLocalDB.java
+++ b/src/main/java/envoy/client/data/PersistentLocalDB.java
@@ -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");
}
/**
diff --git a/src/main/java/envoy/client/data/Settings.java b/src/main/java/envoy/client/data/Settings.java
index 5bff58c..c8bed6a 100644
--- a/src/main/java/envoy/client/data/Settings.java
+++ b/src/main/java/envoy/client/data/Settings.java
@@ -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> items;
- private Map 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> items) { this.items = items; }
-
- /**
- * @return a {@code Map} of all themes with their names as keys
- * @since Envoy Client v0.2-alpha
- */
- public Map getThemes() { return themes; }
-
- /**
- * Sets the {@code Map} 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 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); }
}
diff --git a/src/main/java/envoy/client/data/SettingsItem.java b/src/main/java/envoy/client/data/SettingsItem.java
index 2c9ac39..8638c05 100644
--- a/src/main/java/envoy/client/data/SettingsItem.java
+++ b/src/main/java/envoy/client/data/SettingsItem.java
@@ -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.
*
- * Project: envoy-clientChess
+ * Project: envoy-client
* File: SettingsItem.java
* Created: 23.12.2019
*
@@ -23,19 +19,12 @@ import envoy.client.ui.primary.PrimaryToggleSwitch;
*/
public class SettingsItem implements Serializable {
- private T value;
- private Class extends JComponent> componentClass;
- private String userFriendlyName, description;
+ private T value;
+ private String userFriendlyName, description;
- transient private Consumer changeHandler;
+ private transient Consumer changeHandler;
- private static final Map, 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 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 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
diff --git a/src/main/java/envoy/client/event/HandshakeSuccessfulEvent.java b/src/main/java/envoy/client/event/HandshakeSuccessfulEvent.java
deleted file mode 100644
index a04b41a..0000000
--- a/src/main/java/envoy/client/event/HandshakeSuccessfulEvent.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package envoy.client.event;
-
-import envoy.event.Event;
-
-/**
- * This {@link Event} indicates that a handshake was completed successfully.
- *
- * Project: envoy-client
- * File: HandshakeSuccessfulEvent.java
- * Created: 8 Feb 2020
- *
- * @author Leon Hofmeister
- * @since Envoy Client v0.3-alpha
- */
-public class HandshakeSuccessfulEvent extends Event.Valueless {
-
- private static final long serialVersionUID = 0L;
-}
diff --git a/src/main/java/envoy/client/event/ThemeChangeEvent.java b/src/main/java/envoy/client/event/ThemeChangeEvent.java
index 0967fc0..572476b 100644
--- a/src/main/java/envoy/client/event/ThemeChangeEvent.java
+++ b/src/main/java/envoy/client/event/ThemeChangeEvent.java
@@ -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 {
+public class ThemeChangeEvent extends Event {
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); }
}
diff --git a/src/main/java/envoy/client/net/Client.java b/src/main/java/envoy/client/net/Client.java
index a07c0bc..0d09780 100644
--- a/src/main/java/envoy/client/net/Client.java
+++ b/src/main/java/envoy/client/net/Client.java
@@ -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 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 getUsers() {
+ public Map getUsers() {
checkOnline();
- Map users = new HashMap<>();
- contacts.getContacts().forEach(u -> users.put(u.getName(), u));
+ final Map 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; }
}
diff --git a/src/main/java/envoy/client/net/Receiver.java b/src/main/java/envoy/client/net/Receiver.java
index bdaf853..5926e1e 100644
--- a/src/main/java/envoy/client/net/Receiver.java
+++ b/src/main/java/envoy/client/net/Receiver.java
@@ -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.
+ *
* Project: envoy-client
* File: Receiver.java
* Created: 30.12.2019
@@ -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 void registerProcessor(Class processorClass, Consumer 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(); }
}
diff --git a/src/main/java/envoy/client/net/UserStatusChangeProcessor.java b/src/main/java/envoy/client/net/UserStatusChangeProcessor.java
index 761f7d8..9120662 100644
--- a/src/main/java/envoy/client/net/UserStatusChangeProcessor.java
+++ b/src/main/java/envoy/client/net/UserStatusChangeProcessor.java
@@ -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 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);
}
}
diff --git a/src/main/java/envoy/client/ui/Color.java b/src/main/java/envoy/client/ui/Color.java
deleted file mode 100644
index da58bbb..0000000
--- a/src/main/java/envoy/client/ui/Color.java
+++ /dev/null
@@ -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: envoy-client
- * File: Color.java
- * Created: 23.12.2019
- *
- * @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()); }
-}
diff --git a/src/main/java/envoy/client/ui/ContactListCell.java b/src/main/java/envoy/client/ui/ContactListCell.java
new file mode 100644
index 0000000..c3c51d4
--- /dev/null
+++ b/src/main/java/envoy/client/ui/ContactListCell.java
@@ -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: envoy-client
+ * File: UserListCell.java
+ * Created: 28.03.2020
+ *
+ * @author Kai S. K. Engelbart
+ * @since Envoy Client v0.1-beta
+ */
+public class ContactListCell extends ListCell {
+
+ /**
+ * 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));
+ }
+ }
+}
diff --git a/src/main/java/envoy/client/ui/ContextMenu.java b/src/main/java/envoy/client/ui/ContextMenu.java
deleted file mode 100644
index 64e3abb..0000000
--- a/src/main/java/envoy/client/ui/ContextMenu.java
+++ /dev/null
@@ -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.
- *
- * Project: envoy-client
- * File: ContextMenu.java
- * Created: 17 Mar 2020
- *
- * @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 items = new HashMap<>();
- private Map icons = new HashMap<>();
- private Map 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 itemsWithActions, Map itemIcons,
- Map 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 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 items) { this.items = items; }
-
- /**
- * @return the icons
- * @since Envoy Client v0.1-beta
- */
- public Map getIcons() { return icons; }
-
- /**
- * @param icons the icons to set
- * @since Envoy Client v0.1-beta
- */
- public void setIcons(Map 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 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 mnemonics) { this.mnemonics = mnemonics; }
-}
diff --git a/src/main/java/envoy/client/ui/IconUtil.java b/src/main/java/envoy/client/ui/IconUtil.java
index 0092483..e2577ec 100644
--- a/src/main/java/envoy/client/ui/IconUtil.java
+++ b/src/main/java/envoy/client/ui/IconUtil.java
@@ -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 > EnumMap loadByEnum(Class enumClass, int size) throws IOException {
- var icons = new EnumMap(enumClass);
+ public static > EnumMap loadByEnum(Class enumClass, int size) throws IOException {
+ var icons = new EnumMap(enumClass);
var path = "/icons/" + enumClass.getSimpleName().toLowerCase() + "/";
for (var e : EnumSet.allOf(enumClass))
icons.put(e, load(path + e.toString().toLowerCase() + ".png", size));
diff --git a/src/main/java/envoy/client/ui/MessageListCell.java b/src/main/java/envoy/client/ui/MessageListCell.java
new file mode 100644
index 0000000..60a2ada
--- /dev/null
+++ b/src/main/java/envoy/client/ui/MessageListCell.java
@@ -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.
+ *
+ * Project: envoy-client
+ * File: MessageListCell.java
+ * Created: 28.03.2020
+ *
+ * @author Kai S. K. Engelbart
+ * @since Envoy Client v0.1-beta
+ */
+public class MessageListCell extends ListCell {
+
+ private static final SimpleDateFormat dateFormat = new SimpleDateFormat("dd.MM.yyyy HH:mm");
+ private static Map 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);
+ }
+}
diff --git a/src/main/java/envoy/client/ui/SceneContext.java b/src/main/java/envoy/client/ui/SceneContext.java
new file mode 100644
index 0000000..0c40bf3
--- /dev/null
+++ b/src/main/java/envoy/client/ui/SceneContext.java
@@ -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.
+ *
+ * When a scene is loaded, the style sheet for the current theme is applied to
+ * it.
+ *
+ * Project: envoy-client
+ * File: SceneContext.java
+ * Created: 06.06.2020
+ *
+ * @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 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 the type of the controller
+ * @return the controller used by the current scene
+ * @since Envoy Client v0.1-beta
+ */
+ public 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; }
+}
diff --git a/src/main/java/envoy/client/ui/Startup.java b/src/main/java/envoy/client/ui/Startup.java
new file mode 100644
index 0000000..aff8801
--- /dev/null
+++ b/src/main/java/envoy/client/ui/Startup.java
@@ -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.
+ *
+ * Project: envoy-client
+ * File: Startup.java
+ * Created: 26.03.2020
+ *
+ * @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 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.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); }
+}
diff --git a/src/main/java/envoy/client/ui/Theme.java b/src/main/java/envoy/client/ui/Theme.java
deleted file mode 100755
index 7c4bb79..0000000
--- a/src/main/java/envoy/client/ui/Theme.java
+++ /dev/null
@@ -1,146 +0,0 @@
-package envoy.client.ui;
-
-import java.io.Serializable;
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * Project: envoy-client
- * File: Theme.java
- * Created: 23 Nov 2019
- *
- * @author Maximilian Käfer
- * @since Envoy Client v0.2-alpha
- */
-public class Theme implements Serializable {
-
- private static final long serialVersionUID = 0L;
-
- private String themeName;
- private Map 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} of all colors defined for this theme
- * with their names as keys
- * @since Envoy Client v0.2-alpha
- */
- public Map 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); }
-}
diff --git a/src/main/java/envoy/client/ui/container/ChatWindow.java b/src/main/java/envoy/client/ui/container/ChatWindow.java
deleted file mode 100644
index e3dfd47..0000000
--- a/src/main/java/envoy/client/ui/container/ChatWindow.java
+++ /dev/null
@@ -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: envoy-client
- * File: ChatWindow.java
- * Created: 28 Sep 2019
- *
- * @author Kai S. K. Engelbart
- * @author Maximilian Kä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 userList = new JList<>();
- private DefaultListModel userListModel = new DefaultListModel<>();
- private ComponentList 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 contactsModel = new Model<>();
- private final ComponentList contactList = new ComponentList().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 commands = Map.of("forward selected message", evt -> {
- final Message selectedMessage = messageList.getSingleSelectedElement();
- List 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 selectedUserList = (JList) 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 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 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.
- *
- * 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()); }
-}
diff --git a/src/main/java/envoy/client/ui/container/ContactsChooserDialog.java b/src/main/java/envoy/client/ui/container/ContactsChooserDialog.java
deleted file mode 100755
index 4c81aad..0000000
--- a/src/main/java/envoy/client/ui/container/ContactsChooserDialog.java
+++ /dev/null
@@ -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.
- *
- * Project: envoy-client
- * File: ContactsChooserDialog.java
- * Created: 15 Mar 2020
- *
- * @author Leon Hofmeister
- * @since Envoy Client v0.1-beta
- */
-public class ContactsChooserDialog extends JDialog {
-
- private static final long serialVersionUID = 0L;
-
- private ComponentList contactList = new ComponentList().setModel(new Model())
- .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
- * ArrayList.
- *
- * @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 showForwardingDialog(String title, Component parent, Message message, Collection users) {
- ContactsChooserDialog dialog = new ContactsChooserDialog(parent);
- dialog.setTitle(title);
- dialog.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
- dialog.addCancelButtonActionListener(e -> dialog.dispose());
-
- List results = new ArrayList<>();
- dialog.addOkButtonActionListener(e -> { results.addAll(dialog.getContactList().getSelectedElements()); dialog.dispose(); });
- Model 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 getContactList() { return contactList; }
-
- private void addOkButtonActionListener(ActionListener l) { okButton.addActionListener(l); }
-
- private void addCancelButtonActionListener(ActionListener l) { cancelButton.addActionListener(l); }
-}
diff --git a/src/main/java/envoy/client/ui/container/ContextMenu.java b/src/main/java/envoy/client/ui/container/ContextMenu.java
deleted file mode 100755
index 2043985..0000000
--- a/src/main/java/envoy/client/ui/container/ContextMenu.java
+++ /dev/null
@@ -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.
- *
- * Project: envoy-client
- * File: ContextMenu.java
- * Created: 17 Mar 2020
- *
- * @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 items = new HashMap<>();
- private Map icons = new HashMap<>();
- private Map 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 itemsWithActions, Map itemIcons,
- Map 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 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 items) { this.items = items; }
-
- /**
- * @return the icons
- * @since Envoy Client v0.1-beta
- */
- public Map getIcons() { return icons; }
-
- /**
- * @param icons the icons to set
- * @since Envoy Client v0.1-beta
- */
- public void setIcons(Map 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 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 mnemonics) { this.mnemonics = mnemonics; }
-
- /**
- * {@inheritDoc}
- * 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}
- * 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());
- }
-}
diff --git a/src/main/java/envoy/client/ui/container/LoginDialog.java b/src/main/java/envoy/client/ui/container/LoginDialog.java
deleted file mode 100644
index 0d9f034..0000000
--- a/src/main/java/envoy/client/ui/container/LoginDialog.java
+++ /dev/null
@@ -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: envoy-client
- * File: LoginDialog.java
- * Created: 01.01.2020
- *
- * @author Kai S. K. Engelbart
- * @author Maximilian Kä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 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 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);
- }
-}
diff --git a/src/main/java/envoy/client/ui/container/package-info.java b/src/main/java/envoy/client/ui/container/package-info.java
deleted file mode 100644
index aab40a5..0000000
--- a/src/main/java/envoy/client/ui/container/package-info.java
+++ /dev/null
@@ -1,13 +0,0 @@
-/**
- * This package contains all graphical Containers, like Dialogs and Frames.
- *
- * Project: envoy-client
- * File: package-info.java
- * Created: 16 Mar 2020
- *
- * @author Leon Hofmeister
- * @author Kai S. K. Engelbart
- * @author Maximilian Käfer
- * @since Envoy Client v0.1-beta
- */
-package envoy.client.ui.container;
diff --git a/src/main/java/envoy/client/ui/controller/ChatScene.java b/src/main/java/envoy/client/ui/controller/ChatScene.java
new file mode 100644
index 0000000..6fdbd2f
--- /dev/null
+++ b/src/main/java/envoy/client/ui/controller/ChatScene.java
@@ -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: envoy-client
+ * File: ChatSceneController.java
+ * Created: 26.03.2020
+ *
+ * @author Kai S. K. Engelbart
+ * @since Envoy Client v0.1-beta
+ */
+public final class ChatScene {
+
+ @FXML
+ private Label contactLabel;
+
+ @FXML
+ private ListView messageList;
+
+ @FXML
+ private ListView 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.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.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);
+ }
+ }
+}
diff --git a/src/main/java/envoy/client/ui/controller/ContactSearchScene.java b/src/main/java/envoy/client/ui/controller/ContactSearchScene.java
new file mode 100644
index 0000000..9fc495d
--- /dev/null
+++ b/src/main/java/envoy/client/ui/controller/ContactSearchScene.java
@@ -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: envoy-client
+ * File: ContactSearchSceneController.java
+ * Created: 07.06.2020
+ *
+ * @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 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(); }
+}
diff --git a/src/main/java/envoy/client/ui/controller/LoginScene.java b/src/main/java/envoy/client/ui/controller/LoginScene.java
new file mode 100644
index 0000000..5f6c5aa
--- /dev/null
+++ b/src/main/java/envoy/client/ui/controller/LoginScene.java
@@ -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: envoy-client
+ * File: LoginDialog.java
+ * Created: 03.04.2020
+ *
+ * @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 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 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.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();
+ }
+}
diff --git a/src/main/java/envoy/client/ui/controller/SettingsScene.java b/src/main/java/envoy/client/ui/controller/SettingsScene.java
new file mode 100644
index 0000000..b7cc49d
--- /dev/null
+++ b/src/main/java/envoy/client/ui/controller/SettingsScene.java
@@ -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: envoy-client
+ * File: SettingsSceneController.java
+ * Created: 10.04.2020
+ *
+ * @author Kai S. K. Engelbart
+ * @since Envoy Client v0.1-beta
+ */
+public class SettingsScene {
+
+ @FXML
+ private ListView 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(); }
+}
diff --git a/src/main/java/envoy/client/ui/controller/package-info.java b/src/main/java/envoy/client/ui/controller/package-info.java
new file mode 100644
index 0000000..7989b8f
--- /dev/null
+++ b/src/main/java/envoy/client/ui/controller/package-info.java
@@ -0,0 +1,11 @@
+/**
+ * Contains JavaFX scene controllers.
+ *
+ * Project: envoy-client
+ * File: package-info.java
+ * Created: 08.06.2020
+ *
+ * @author Kai S. K. Engelbart
+ * @since Envoy Client v0.1-beta
+ */
+package envoy.client.ui.controller;
diff --git a/src/main/java/envoy/client/ui/list/ComponentList.java b/src/main/java/envoy/client/ui/list/ComponentList.java
deleted file mode 100644
index c3cb2ec..0000000
--- a/src/main/java/envoy/client/ui/list/ComponentList.java
+++ /dev/null
@@ -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.
- *
- * Project: envoy-client
- * File: ComponentList.java
- * Created: 25.01.2020
- *
- * @param the type of object displayed in this list
- * @author Kai S. K. Engelbart
- * @since Envoy Client v0.3-alpha
- */
-public class ComponentList extends JPanel {
-
- private Model model;
- private Renderer renderer;
- private SelectionHandler selectionHandler;
- private SelectionMode selectionMode = SelectionMode.NONE;
- private Set 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 getSelection() { return selection; }
-
- /**
- * @return a set of all selected elements
- * @since Envoy Client v0.1-beta
- */
- public Set getSelectedElements() {
- var selectedElements = new HashSet();
- 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 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 setModel(Model 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 getRenderer() { return renderer; }
-
- /**
- * @param renderer the renderer to set
- * @return this component list
- * @since Envoy Client v0.1-beta
- */
- public ComponentList setRenderer(Renderer 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 setSelectionMode(SelectionMode selectionMode) {
- this.selectionMode = selectionMode;
- clearSelection();
- return this;
- }
-
- /**
- * @return the selection handler
- * @since Envoy Client v0.1-beta
- */
- public SelectionHandler getSelectionHandler() { return selectionHandler; }
-
- /**
- * @param selectionHandler the selection handler to set
- * @since Envoy Client v0.1-beta
- */
- public void setSelectionHandler(SelectionHandler selectionHandler) { this.selectionHandler = selectionHandler; }
-}
diff --git a/src/main/java/envoy/client/ui/list/Model.java b/src/main/java/envoy/client/ui/list/Model.java
deleted file mode 100644
index 4f13636..0000000
--- a/src/main/java/envoy/client/ui/list/Model.java
+++ /dev/null
@@ -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}.
- *
- * Project: envoy-client
- * File: Model.java
- * Created: 25.01.2020
- *
- * @param the type of object displayed in this list
- * @author Kai S. K. Engelbart
- * @since Envoy Client v0.3-alpha
- */
-public final class Model implements Iterable, Serializable {
-
- private List elements = new ArrayList<>();
- transient private ComponentList 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 iterator() {
- return new Iterator<>() {
-
- Iterator 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 componentList) {
- this.componentList = componentList;
- if (componentList != null && componentList.getRenderer() != null) componentList.synchronizeModel();
- }
-}
diff --git a/src/main/java/envoy/client/ui/list/Renderer.java b/src/main/java/envoy/client/ui/list/Renderer.java
deleted file mode 100644
index f2d3ad9..0000000
--- a/src/main/java/envoy/client/ui/list/Renderer.java
+++ /dev/null
@@ -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.
- *
- * Project: envoy-client
- * File: Renderer.java
- * Created: 25.01.2020
- *
- * @param the type of object displayed in this list
- * @author Kai S. K. Engelbart
- * @since Envoy Client v0.3-alpha
- */
-@FunctionalInterface
-public interface Renderer {
-
- /**
- * 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);
-}
diff --git a/src/main/java/envoy/client/ui/list/SelectionHandler.java b/src/main/java/envoy/client/ui/list/SelectionHandler.java
deleted file mode 100644
index d8c1984..0000000
--- a/src/main/java/envoy/client/ui/list/SelectionHandler.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package envoy.client.ui.list;
-
-import javax.swing.JComponent;
-
-/**
- * Handles the selection of elements in a {@link ComponentList}.
- *
- * Project: envoy-client
- * File: SelectionHandler.java
- * Created: 21.03.2020
- *
- * @author Kai S. K. Engelbart
- * @param the type of the underlying {@link ComponentList}
- * @since Envoy Client v0.1-beta
- */
-@FunctionalInterface
-public interface SelectionHandler {
-
- /**
- * 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);
-}
diff --git a/src/main/java/envoy/client/ui/list/package-info.java b/src/main/java/envoy/client/ui/list/package-info.java
deleted file mode 100644
index 50d553b..0000000
--- a/src/main/java/envoy/client/ui/list/package-info.java
+++ /dev/null
@@ -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äfer
- * @since Envoy Client v0.3-alpha
- */
-package envoy.client.ui.list;
diff --git a/src/main/java/envoy/client/ui/list_component/ContactSearchComponent.java b/src/main/java/envoy/client/ui/list_component/ContactSearchComponent.java
deleted file mode 100644
index edba839..0000000
--- a/src/main/java/envoy/client/ui/list_component/ContactSearchComponent.java
+++ /dev/null
@@ -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: envoy-client
- * File: ContactSearchComponent.java
- * Created: 21.03.2020
- *
- * @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);
- }
-}
diff --git a/src/main/java/envoy/client/ui/list_component/MessageComponent.java b/src/main/java/envoy/client/ui/list_component/MessageComponent.java
deleted file mode 100644
index 9ceb413..0000000
--- a/src/main/java/envoy/client/ui/list_component/MessageComponent.java
+++ /dev/null
@@ -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: envoy-client
- * File: MessageComponent.java
- * Created: 21.03.2020
- *
- * @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 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);
- }
-}
diff --git a/src/main/java/envoy/client/ui/list_component/UserComponent.java b/src/main/java/envoy/client/ui/list_component/UserComponent.java
deleted file mode 100644
index 4d3332e..0000000
--- a/src/main/java/envoy/client/ui/list_component/UserComponent.java
+++ /dev/null
@@ -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}.
- *
- * Project: envoy-client
- * File: UserComponent.java
- * Created: 21.03.2020
- *
- * @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);
- }
-}
diff --git a/src/main/java/envoy/client/ui/list_component/package-info.java b/src/main/java/envoy/client/ui/list_component/package-info.java
deleted file mode 100644
index 89da45c..0000000
--- a/src/main/java/envoy/client/ui/list_component/package-info.java
+++ /dev/null
@@ -1,14 +0,0 @@
-/**
- * This package contains swing components that can be displayed by
- * {@link envoy.client.ui.list.ComponentList}.
- *
- * Project: envoy-client
- * File: package-info.java
- * Created: 21 Mar 2020
- *
- * @author Leon Hofmeister
- * @author Kai S. K. Engelbart
- * @author Maximilian Käfer
- * @since Envoy Client v0.1-beta
- */
-package envoy.client.ui.list_component;
diff --git a/src/main/java/envoy/client/ui/package-info.java b/src/main/java/envoy/client/ui/package-info.java
index 10bf4ee..9f94ac2 100644
--- a/src/main/java/envoy/client/ui/package-info.java
+++ b/src/main/java/envoy/client/ui/package-info.java
@@ -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äfer
* @since Envoy Client v0.1-beta
*/
diff --git a/src/main/java/envoy/client/ui/primary/PrimaryButton.java b/src/main/java/envoy/client/ui/primary/PrimaryButton.java
deleted file mode 100644
index de7852c..0000000
--- a/src/main/java/envoy/client/ui/primary/PrimaryButton.java
+++ /dev/null
@@ -1,63 +0,0 @@
-package envoy.client.ui.primary;
-
-import java.awt.Graphics;
-
-import javax.swing.JButton;
-
-/**
- * Project: envoy-client
- * File: PrimaryButton.javaEvent.java
- * Created: 07.12.2019
- *
- * @author Kai S. K. Engelbart
- * @author Maximilian Kä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; }
-}
diff --git a/src/main/java/envoy/client/ui/primary/PrimaryScrollBar.java b/src/main/java/envoy/client/ui/primary/PrimaryScrollBar.java
deleted file mode 100644
index 371bd05..0000000
--- a/src/main/java/envoy/client/ui/primary/PrimaryScrollBar.java
+++ /dev/null
@@ -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: envoy-client
- * File: PrimaryScrollBar.java
- * Created: 14.12.2019
- *
- * @author Maximilian Kä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();
- }
-}
diff --git a/src/main/java/envoy/client/ui/primary/PrimaryScrollPane.java b/src/main/java/envoy/client/ui/primary/PrimaryScrollPane.java
deleted file mode 100644
index e02502b..0000000
--- a/src/main/java/envoy/client/ui/primary/PrimaryScrollPane.java
+++ /dev/null
@@ -1,85 +0,0 @@
-package envoy.client.ui.primary;
-
-import javax.swing.JScrollPane;
-
-import envoy.client.ui.Theme;
-
-/**
- * Project: envoy-client
- * File: PrimaryScrollPane.java
- * Created: 15 Dec 2019
- *
- * @author Kai S. K. Engelbart
- * @author Maximilian Kä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 autoscroll functionality for the vertical scroll bar.
- *
- * Functionality to automatically scroll down when user views
- * the bottom of the chat while there are new messages added.
- *
- * When chat is opened, the vertical scroll bar starts at the bottom.
- *
- * When rereading messages, the chat doesn't scroll down if new messages
- * 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; }
-}
diff --git a/src/main/java/envoy/client/ui/primary/PrimaryTextArea.java b/src/main/java/envoy/client/ui/primary/PrimaryTextArea.java
deleted file mode 100644
index 37d05eb..0000000
--- a/src/main/java/envoy/client/ui/primary/PrimaryTextArea.java
+++ /dev/null
@@ -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: envoy-client
- * File: PrimaryTextArea.javaEvent.java
- * Created: 07.12.2019
- *
- * @author Maximilian Kä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; }
-}
diff --git a/src/main/java/envoy/client/ui/primary/PrimaryToggleSwitch.java b/src/main/java/envoy/client/ui/primary/PrimaryToggleSwitch.java
deleted file mode 100644
index 87afed9..0000000
--- a/src/main/java/envoy/client/ui/primary/PrimaryToggleSwitch.java
+++ /dev/null
@@ -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}.
- *
- * Project: envoy-client
- * File: PrimaryToggleSwitch.java
- * Created: 21 Dec 2019
- *
- * @author Maximilian Kä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 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);
- }
-}
diff --git a/src/main/java/envoy/client/ui/primary/package-info.java b/src/main/java/envoy/client/ui/primary/package-info.java
deleted file mode 100644
index d4c54fa..0000000
--- a/src/main/java/envoy/client/ui/primary/package-info.java
+++ /dev/null
@@ -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.
- * Primary elements are supposed to provide the main functionality of a UI
- * component.
- *
- * Project: envoy-client
- * File: package-info.java
- * Created: 14 Mar 2020
- *
- * @author Leon Hofmeister
- * @author Kai S. K. Engelbart
- * @author Maximilian Käfer
- * @since Envoy Client v0.1-beta
- */
-package envoy.client.ui.primary;
diff --git a/src/main/java/envoy/client/ui/renderer/UserListRenderer.java b/src/main/java/envoy/client/ui/renderer/UserListRenderer.java
deleted file mode 100644
index 70e8e72..0000000
--- a/src/main/java/envoy/client/ui/renderer/UserListRenderer.java
+++ /dev/null
@@ -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.
- *
- * Project: envoy-client
- * File: UserListRenderer.java
- * Created: 12 Oct 2019
- *
- * @author Kai S. K. Engelbart
- * @author Maximilian Käfer
- * @since Envoy Client v0.1-alpha
- */
-public class UserListRenderer extends JLabel implements ListCellRenderer {
-
- 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("