Compare commits
77 Commits
Author | SHA1 | Date | |
---|---|---|---|
6499a4f698 | |||
05ed5da41b | |||
c5f4969666 | |||
1a9f9a85ab | |||
544210a811 | |||
5ef5d96445 | |||
dcf1b0c58d | |||
10213a0d3d | |||
b653652f6d | |||
0ff910ebde | |||
6d85e337d2 | |||
67ebc6be83 | |||
e3052a2133 | |||
4d4865570d | |||
0ce8b0c89d | |||
cd7793a589 | |||
e5659c1da1 | |||
f67ca1d61d | |||
8bdd201b28 | |||
f6c772a655 | |||
7a883861be | |||
d4c7813c97 | |||
889e9b186f | |||
fccd7e70b1 | |||
2eeb55ed52 | |||
44d3082958 | |||
241e5def03 | |||
cd8971b6b4 | |||
e79f60e95e | |||
aaaf5ef7be | |||
98f59c1383 | |||
db28f02505 | |||
b2c3cf62c8 | |||
77a75fc37c | |||
a0812f193e | |||
ebe19c00c9 | |||
dd477b6cbc | |||
571a953c40 | |||
a515ec961a | |||
12184848b6 | |||
2e17caea4d | |||
44f4d8f1e0 | |||
5b85c1bf54 | |||
f4f34ff829 | |||
ab2e9aa114 | |||
75f0a65517 | |||
08bd915f04 | |||
fa2a5d0b24 | |||
1d191858fe | |||
3c8c544cbd | |||
e8202e0c94 | |||
3810fdef02 | |||
637ad9f61f | |||
f2eb89d469 | |||
6f9982bbc3 | |||
5e1b9a9d5b | |||
fb1147f939 | |||
7ca770cbc3 | |||
da6bdafc68 | |||
99867eb23a | |||
994cbbcd72 | |||
51b189e8f5 | |||
3d987985ff | |||
5f0910635a | |||
ab77c98a36 | |||
434d577c15 | |||
8c0add517a | |||
9934eefd41 | |||
8543e94040 | |||
8592839156 | |||
7fffa0da83 | |||
85d0aa37d2 | |||
80795a3fc2 | |||
f5bfb73abe | |||
2779971e99 | |||
a4e9474b97 | |||
3f0267624c |
37
Jenkinsfile
vendored
Normal file
37
Jenkinsfile
vendored
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
pipeline {
|
||||||
|
agent any
|
||||||
|
|
||||||
|
options {
|
||||||
|
ansiColor('xterm')
|
||||||
|
}
|
||||||
|
|
||||||
|
stages {
|
||||||
|
stage('Build') {
|
||||||
|
steps {
|
||||||
|
sh 'mvn -DskipTests clean package'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Test') {
|
||||||
|
steps {
|
||||||
|
sh 'mvn test'
|
||||||
|
}
|
||||||
|
post {
|
||||||
|
always {
|
||||||
|
junit '*/target/surefire-reports/*.xml'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('SonarQube Analysis') {
|
||||||
|
steps {
|
||||||
|
withSonarQubeEnv('KSKE SonarQube') {
|
||||||
|
sh 'mvn org.sonarsource.scanner.maven:sonar-maven-plugin:3.9.1.2184:sonar'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
post {
|
||||||
|
success {
|
||||||
|
archiveArtifacts artifacts: 'client/target/envoy-client-*-shaded.jar, server/target/envoy-server-jar-with-dependencies.jar'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
23
README.md
23
README.md
@ -17,11 +17,30 @@ If you want to transfer a file to another user, you can attach it to a message.
|
|||||||
|
|
||||||
On the settings page some convenience features can be configured, as well as the color theme.
|
On the settings page some convenience features can be configured, as well as the color theme.
|
||||||
|
|
||||||
|
Additional info on how to use Envoy can be found [here](https://git.kske.dev/zdm/envoy/wiki) in the client section.
|
||||||
|
|
||||||
|
### System requirements
|
||||||
|
|
||||||
|
To run Envoy, you have to install a Java Runtime Environment (JRE) of at least version 11.
|
||||||
|
You can download an open source implementation from [here](https://jdk.java.net/15/).
|
||||||
|
|
||||||
|
If you are running a Linux distribution, make sure that an emoji font like [Noto emoji](https://github.com/googlefonts/noto-emoji) is installed.
|
||||||
|
Most major Linux distributions like Debian, Arch and Gentoo have a Noto emoji package available inside their package repositories.
|
||||||
|
|
||||||
## Server Administrator
|
## Server Administrator
|
||||||
|
|
||||||
To set up an Envoy server, download the package from the release page.
|
To set up an Envoy server, download the package from the release page.
|
||||||
|
|
||||||
Because the project lacks external documentation for the moment, please refer to the Javadoc inside the source code to configure your Envoy instance.
|
To configure the behavior of Envoy Server, please have a look at the [documentation](https://git.kske.dev/zdm/envoy/wiki), specifically the server part.
|
||||||
|
|
||||||
|
### System requirements
|
||||||
|
|
||||||
|
To run Envoy server, you have to install a JRE as mentioned above, as well as a database.
|
||||||
|
In development, PostgreSQL is used, which you can download from [here](https://www.postgresql.org/download/).
|
||||||
|
|
||||||
|
Look at the file `META-INF/persistence.xml` inside `envoy-server.jar` for the database configuration.
|
||||||
|
|
||||||
|
After creating a database and configuring the credentials, the server will initialize the necessary tables automatically.
|
||||||
|
|
||||||
## Programmer
|
## Programmer
|
||||||
|
|
||||||
@ -48,5 +67,5 @@ Envoy is organized as a Maven project that is split into three modules.
|
|||||||
* Non-blocking connectivity infrastructure based on `java.nio`
|
* Non-blocking connectivity infrastructure based on `java.nio`
|
||||||
* Processors to handle incoming events
|
* Processors to handle incoming events
|
||||||
* Database connectivity
|
* Database connectivity
|
||||||
* Databse entities
|
* Database entities
|
||||||
* Utility classes to check client version compatability and password validity
|
* Utility classes to check client version compatability and password validity
|
||||||
|
@ -129,364 +129,4 @@ org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
|
|||||||
org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
|
org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
|
||||||
org.eclipse.jdt.core.compiler.release=disabled
|
org.eclipse.jdt.core.compiler.release=disabled
|
||||||
org.eclipse.jdt.core.compiler.source=11
|
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=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_if_empty
|
|
||||||
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_if_empty
|
|
||||||
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
|
org.eclipse.jdt.core.javaFormatter=org.eclipse.jdt.core.defaultJavaFormatter
|
||||||
|
File diff suppressed because one or more lines are too long
@ -21,12 +21,12 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.openjfx</groupId>
|
<groupId>org.openjfx</groupId>
|
||||||
<artifactId>javafx-controls</artifactId>
|
<artifactId>javafx-controls</artifactId>
|
||||||
<version>11.0.2</version>
|
<version>15</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.openjfx</groupId>
|
<groupId>org.openjfx</groupId>
|
||||||
<artifactId>javafx-fxml</artifactId>
|
<artifactId>javafx-fxml</artifactId>
|
||||||
<version>11.0.2</version>
|
<version>15</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
@ -7,8 +7,8 @@ import envoy.client.ui.Startup;
|
|||||||
/**
|
/**
|
||||||
* Triggers application startup.
|
* Triggers application startup.
|
||||||
* <p>
|
* <p>
|
||||||
* To allow Maven shading, the main method has to be separated from the
|
* To allow Maven shading, the main method has to be separated from the {@link Startup} class which
|
||||||
* {@link Startup} class which extends {@link Application}.
|
* extends {@link Application}.
|
||||||
*
|
*
|
||||||
* @author Kai S. K. Engelbart
|
* @author Kai S. K. Engelbart
|
||||||
* @since Envoy Client v0.1-beta
|
* @since Envoy Client v0.1-beta
|
||||||
@ -25,8 +25,7 @@ public final class Main {
|
|||||||
/**
|
/**
|
||||||
* Starts the application.
|
* Starts the application.
|
||||||
*
|
*
|
||||||
* @param args the command line arguments are processed by the
|
* @param args the command line arguments are processed by the client configuration
|
||||||
* client configuration
|
|
||||||
* @since Envoy Client v0.1-beta
|
* @since Envoy Client v0.1-beta
|
||||||
*/
|
*/
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
|
@ -35,7 +35,9 @@ public final class Cache<T> implements Consumer<T>, Serializable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() { return String.format("Cache[elements=" + elements + "]"); }
|
public String toString() {
|
||||||
|
return String.format("Cache[elements=" + elements + "]");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the processor to which cached elements are relayed.
|
* Sets the processor to which cached elements are relayed.
|
||||||
@ -52,7 +54,8 @@ public final class Cache<T> implements Consumer<T>, Serializable {
|
|||||||
* @since Envoy Client v0.3-alpha
|
* @since Envoy Client v0.3-alpha
|
||||||
*/
|
*/
|
||||||
public void relay() {
|
public void relay() {
|
||||||
if (processor == null) throw new IllegalStateException("Processor is not defined");
|
if (processor == null)
|
||||||
|
throw new IllegalStateException("Processor is not defined");
|
||||||
elements.forEach(processor::accept);
|
elements.forEach(processor::accept);
|
||||||
elements.clear();
|
elements.clear();
|
||||||
}
|
}
|
||||||
@ -62,5 +65,7 @@ public final class Cache<T> implements Consumer<T>, Serializable {
|
|||||||
*
|
*
|
||||||
* @since Envoy Client v0.2-beta
|
* @since Envoy Client v0.2-beta
|
||||||
*/
|
*/
|
||||||
public void clear() { elements.clear(); }
|
public void clear() {
|
||||||
|
elements.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,8 +4,7 @@ import java.io.Serializable;
|
|||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores a heterogeneous map of {@link Cache} objects with different type
|
* Stores a heterogeneous map of {@link Cache} objects with different type parameters.
|
||||||
* parameters.
|
|
||||||
*
|
*
|
||||||
* @author Kai S. K. Engelbart
|
* @author Kai S. K. Engelbart
|
||||||
* @since Envoy Client v0.1-beta
|
* @since Envoy Client v0.1-beta
|
||||||
@ -24,7 +23,9 @@ public final class CacheMap implements Serializable {
|
|||||||
* @param cache the cache to store
|
* @param cache the cache to store
|
||||||
* @since Envoy Client v0.1-beta
|
* @since Envoy Client v0.1-beta
|
||||||
*/
|
*/
|
||||||
public <T> void put(Class<T> key, Cache<T> cache) { map.put(key, cache); }
|
public <T> void put(Class<T> key, Cache<T> cache) {
|
||||||
|
map.put(key, cache);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a cache mapped by a class.
|
* Returns a cache mapped by a class.
|
||||||
@ -34,7 +35,9 @@ public final class CacheMap implements Serializable {
|
|||||||
* @return the cache
|
* @return the cache
|
||||||
* @since Envoy Client v0.1-beta
|
* @since Envoy Client v0.1-beta
|
||||||
*/
|
*/
|
||||||
public <T> Cache<T> get(Class<T> key) { return (Cache<T>) map.get(key); }
|
public <T> Cache<T> get(Class<T> key) {
|
||||||
|
return (Cache<T>) map.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a cache mapped by a class or any of its subclasses.
|
* Returns a cache mapped by a class or any of its subclasses.
|
||||||
@ -64,5 +67,7 @@ public final class CacheMap implements Serializable {
|
|||||||
*
|
*
|
||||||
* @since Envoy Client v0.2-beta
|
* @since Envoy Client v0.2-beta
|
||||||
*/
|
*/
|
||||||
public void clear() { map.values().forEach(Cache::clear); }
|
public void clear() {
|
||||||
|
map.values().forEach(Cache::clear);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,16 +3,17 @@ package envoy.client.data;
|
|||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
|
import javafx.beans.property.*;
|
||||||
import javafx.collections.*;
|
import javafx.collections.*;
|
||||||
|
|
||||||
import envoy.client.net.WriteProxy;
|
|
||||||
import envoy.data.*;
|
import envoy.data.*;
|
||||||
import envoy.data.Message.MessageStatus;
|
import envoy.data.Message.MessageStatus;
|
||||||
import envoy.event.MessageStatusChange;
|
import envoy.event.MessageStatusChange;
|
||||||
|
|
||||||
|
import envoy.client.net.WriteProxy;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a chat between two {@link User}s
|
* Represents a chat between two {@link User}s as a list of {@link Message} objects.
|
||||||
* as a list of {@link Message} objects.
|
|
||||||
*
|
*
|
||||||
* @author Maximilian Käfer
|
* @author Maximilian Käfer
|
||||||
* @author Leon Hofmeister
|
* @author Leon Hofmeister
|
||||||
@ -21,17 +22,21 @@ import envoy.event.MessageStatusChange;
|
|||||||
*/
|
*/
|
||||||
public class Chat implements Serializable {
|
public class Chat implements Serializable {
|
||||||
|
|
||||||
protected final Contact recipient;
|
|
||||||
|
|
||||||
protected transient ObservableList<Message> messages = FXCollections.observableArrayList();
|
protected transient ObservableList<Message> messages = FXCollections.observableArrayList();
|
||||||
|
|
||||||
protected int unreadAmount;
|
protected final Contact recipient;
|
||||||
|
|
||||||
|
protected boolean disabled;
|
||||||
|
protected boolean underlyingContactDeleted;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores the last time an {@link envoy.event.IsTyping} event has been sent.
|
* Stores the last time an {@link envoy.event.IsTyping} event has been sent.
|
||||||
*/
|
*/
|
||||||
protected transient long lastWritingEvent;
|
protected transient long lastWritingEvent;
|
||||||
|
|
||||||
|
protected int unreadAmount;
|
||||||
|
protected static IntegerProperty totalUnreadAmount = new SimpleIntegerProperty();
|
||||||
|
|
||||||
private static final long serialVersionUID = 2L;
|
private static final long serialVersionUID = 2L;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -42,11 +47,14 @@ public class Chat implements Serializable {
|
|||||||
* @param recipient the user who receives the messages
|
* @param recipient the user who receives the messages
|
||||||
* @since Envoy Client v0.1-alpha
|
* @since Envoy Client v0.1-alpha
|
||||||
*/
|
*/
|
||||||
public Chat(Contact recipient) { this.recipient = recipient; }
|
public Chat(Contact recipient) {
|
||||||
|
this.recipient = recipient;
|
||||||
|
}
|
||||||
|
|
||||||
private void readObject(ObjectInputStream stream) throws ClassNotFoundException, IOException {
|
private void readObject(ObjectInputStream stream) throws ClassNotFoundException, IOException {
|
||||||
stream.defaultReadObject();
|
stream.defaultReadObject();
|
||||||
messages = FXCollections.observableList((List<Message>) stream.readObject());
|
messages = FXCollections.observableList((List<Message>) stream.readObject());
|
||||||
|
totalUnreadAmount.set(totalUnreadAmount.get() + unreadAmount);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeObject(ObjectOutputStream stream) throws IOException {
|
private void writeObject(ObjectOutputStream stream) throws IOException {
|
||||||
@ -55,7 +63,14 @@ public class Chat implements Serializable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() { return String.format("%s[recipient=%s,messages=%d]", getClass().getSimpleName(), recipient, messages.size()); }
|
public String toString() {
|
||||||
|
return String.format(
|
||||||
|
"%s[recipient=%s,messages=%d,disabled=%b]",
|
||||||
|
getClass().getSimpleName(),
|
||||||
|
recipient,
|
||||||
|
messages.size(),
|
||||||
|
disabled);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a hash code based on the recipient.
|
* Generates a hash code based on the recipient.
|
||||||
@ -63,7 +78,9 @@ public class Chat implements Serializable {
|
|||||||
* @since Envoy Client v0.1-beta
|
* @since Envoy Client v0.1-beta
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() { return Objects.hash(recipient); }
|
public int hashCode() {
|
||||||
|
return Objects.hash(recipient);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests equality to another object based on the recipient.
|
* Tests equality to another object based on the recipient.
|
||||||
@ -72,39 +89,46 @@ public class Chat implements Serializable {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object obj) {
|
public boolean equals(Object obj) {
|
||||||
if (this == obj) return true;
|
if (this == obj)
|
||||||
if (!(obj instanceof Chat)) return false;
|
return true;
|
||||||
final Chat other = (Chat) obj;
|
if (!(obj instanceof Chat))
|
||||||
|
return false;
|
||||||
|
final var other = (Chat) obj;
|
||||||
return Objects.equals(recipient, other.recipient);
|
return Objects.equals(recipient, other.recipient);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the status of all chat messages received from the recipient to
|
* Sets the status of all chat messages received from the recipient to {@code READ} starting
|
||||||
* {@code READ} starting from the bottom and stopping once a read message is
|
* from the bottom and stopping once a read message is found.
|
||||||
* found.
|
|
||||||
*
|
*
|
||||||
* @param writeProxy the write proxy instance used to notify the server about
|
* @param writeProxy the write proxy instance used to notify the server about the message status
|
||||||
* the message status changes
|
* changes
|
||||||
* @since Envoy Client v0.3-alpha
|
* @since Envoy Client v0.3-alpha
|
||||||
*/
|
*/
|
||||||
public void read(WriteProxy writeProxy) {
|
public void read(WriteProxy writeProxy) {
|
||||||
for (int i = messages.size() - 1; i >= 0; --i) {
|
for (int i = messages.size() - 1; i >= 0; --i) {
|
||||||
final Message m = messages.get(i);
|
final var m = messages.get(i);
|
||||||
if (m.getSenderID() == recipient.getID()) if (m.getStatus() == MessageStatus.READ) break;
|
if (m.getSenderID() == recipient.getID())
|
||||||
|
if (m.getStatus() == MessageStatus.READ)
|
||||||
|
break;
|
||||||
else {
|
else {
|
||||||
m.setStatus(MessageStatus.READ);
|
m.setStatus(MessageStatus.READ);
|
||||||
writeProxy.writeMessageStatusChange(new MessageStatusChange(m));
|
writeProxy.writeMessageStatusChange(new MessageStatusChange(m));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
totalUnreadAmount.set(totalUnreadAmount.get() - unreadAmount);
|
||||||
unreadAmount = 0;
|
unreadAmount = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return {@code true} if the newest message received in the chat doesn't have
|
* @return {@code true} if the newest message received in the chat doesn't have the status
|
||||||
* the status {@code READ}
|
* {@code READ}
|
||||||
* @since Envoy Client v0.3-alpha
|
* @since Envoy Client v0.3-alpha
|
||||||
*/
|
*/
|
||||||
public boolean isUnread() { return !messages.isEmpty() && messages.get(messages.size() - 1).getStatus() != MessageStatus.READ; }
|
public boolean isUnread() {
|
||||||
|
return !messages.isEmpty()
|
||||||
|
&& messages.get(messages.size() - 1).getStatus() != MessageStatus.READ;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inserts a message at the correct place according to its creation date.
|
* Inserts a message at the correct place according to its creation date.
|
||||||
@ -121,12 +145,32 @@ public class Chat implements Serializable {
|
|||||||
messages.add(0, message);
|
messages.add(0, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the message with the given ID.
|
||||||
|
*
|
||||||
|
* @param messageID the ID of the message to remove
|
||||||
|
* @return whether the message has been found and removed
|
||||||
|
* @since Envoy Client v0.3-beta
|
||||||
|
*/
|
||||||
|
public boolean remove(long messageID) {
|
||||||
|
return messages.removeIf(m -> m.getID() == messageID);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return an integer property storing the total amount of unread messages
|
||||||
|
* @since Envoy Client v0.3-beta
|
||||||
|
*/
|
||||||
|
public static IntegerProperty getTotalUnreadAmount() { return totalUnreadAmount; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Increments the amount of unread messages.
|
* Increments the amount of unread messages.
|
||||||
*
|
*
|
||||||
* @since Envoy Client v0.1-beta
|
* @since Envoy Client v0.1-beta
|
||||||
*/
|
*/
|
||||||
public void incrementUnreadAmount() { unreadAmount++; }
|
public void incrementUnreadAmount() {
|
||||||
|
++unreadAmount;
|
||||||
|
totalUnreadAmount.set(totalUnreadAmount.get() + 1);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the amount of unread messages in this chat
|
* @return the amount of unread messages in this chat
|
||||||
@ -147,8 +191,7 @@ public class Chat implements Serializable {
|
|||||||
public Contact getRecipient() { return recipient; }
|
public Contact getRecipient() { return recipient; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the last known time a {@link envoy.event.IsTyping} event has been
|
* @return the last known time a {@link envoy.event.IsTyping} event has been sent
|
||||||
* sent
|
|
||||||
* @since Envoy Client v0.2-beta
|
* @since Envoy Client v0.2-beta
|
||||||
*/
|
*/
|
||||||
public long getLastWritingEvent() { return lastWritingEvent; }
|
public long getLastWritingEvent() { return lastWritingEvent; }
|
||||||
@ -158,5 +201,25 @@ public class Chat implements Serializable {
|
|||||||
*
|
*
|
||||||
* @since Envoy Client v0.2-beta
|
* @since Envoy Client v0.2-beta
|
||||||
*/
|
*/
|
||||||
public void lastWritingEventWasNow() { lastWritingEvent = System.currentTimeMillis(); }
|
public void lastWritingEventWasNow() {
|
||||||
|
lastWritingEvent = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether messages can be sent in this chat. Should be {@code true} i.e. for chats
|
||||||
|
* whose recipient deleted this client as a contact.
|
||||||
|
*
|
||||||
|
* @return whether this chat has been disabled
|
||||||
|
* @since Envoy Client v0.3-beta
|
||||||
|
*/
|
||||||
|
public boolean isDisabled() { return disabled; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether messages can be sent in this chat. Should be true i.e. for chats whose
|
||||||
|
* recipient deleted this client as a contact.
|
||||||
|
*
|
||||||
|
* @param disabled whether this chat should be disabled
|
||||||
|
* @since Envoy Client v0.3-beta
|
||||||
|
*/
|
||||||
|
public void setDisabled(boolean disabled) { this.disabled = disabled; }
|
||||||
}
|
}
|
||||||
|
@ -5,8 +5,8 @@ import static java.util.function.Function.identity;
|
|||||||
import envoy.data.Config;
|
import envoy.data.Config;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements a configuration specific to the Envoy Client with default values
|
* Implements a configuration specific to the Envoy Client with default values and convenience
|
||||||
* and convenience methods.
|
* methods.
|
||||||
*
|
*
|
||||||
* @author Kai S. K. Engelbart
|
* @author Kai S. K. Engelbart
|
||||||
* @since Envoy Client v0.1-beta
|
* @since Envoy Client v0.1-beta
|
||||||
@ -20,7 +20,8 @@ public final class ClientConfig extends Config {
|
|||||||
* @since Envoy Client v0.1-beta
|
* @since Envoy Client v0.1-beta
|
||||||
*/
|
*/
|
||||||
public static ClientConfig getInstance() {
|
public static ClientConfig getInstance() {
|
||||||
if (config == null) config = new ClientConfig();
|
if (config == null)
|
||||||
|
config = new ClientConfig();
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,5 +48,7 @@ public final class ClientConfig extends Config {
|
|||||||
* @return the amount of minutes after which the local database should be saved
|
* @return the amount of minutes after which the local database should be saved
|
||||||
* @since Envoy Client v0.2-beta
|
* @since Envoy Client v0.2-beta
|
||||||
*/
|
*/
|
||||||
public Integer getLocalDBSaveInterval() { return (Integer) items.get("localDBSaveInterval").get(); }
|
public Integer getLocalDBSaveInterval() {
|
||||||
|
return (Integer) items.get("localDBSaveInterval").get();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,8 @@ public class Context {
|
|||||||
* @since Envoy Client v0.2-beta
|
* @since Envoy Client v0.2-beta
|
||||||
*/
|
*/
|
||||||
public void initWriteProxy() {
|
public void initWriteProxy() {
|
||||||
if (localDB == null) throw new IllegalStateException("The LocalDB has to be initialized!");
|
if (localDB == null)
|
||||||
|
throw new IllegalStateException("The LocalDB has to be initialized!");
|
||||||
writeProxy = new WriteProxy(client, localDB);
|
writeProxy = new WriteProxy(client, localDB);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,14 +2,14 @@ package envoy.client.data;
|
|||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
|
||||||
import envoy.client.net.WriteProxy;
|
|
||||||
import envoy.data.*;
|
import envoy.data.*;
|
||||||
import envoy.data.Message.MessageStatus;
|
import envoy.data.Message.MessageStatus;
|
||||||
import envoy.event.GroupMessageStatusChange;
|
import envoy.event.GroupMessageStatusChange;
|
||||||
|
|
||||||
|
import envoy.client.net.WriteProxy;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a chat between a user and a group
|
* Represents a chat between a user and a group as a list of messages.
|
||||||
* as a list of messages.
|
|
||||||
*
|
*
|
||||||
* @author Maximilian Käfer
|
* @author Maximilian Käfer
|
||||||
* @since Envoy Client v0.1-beta
|
* @since Envoy Client v0.1-beta
|
||||||
@ -25,7 +25,7 @@ public final class GroupChat extends Chat {
|
|||||||
* @param recipient the group whose members receive the messages
|
* @param recipient the group whose members receive the messages
|
||||||
* @since Envoy Client v0.1-beta
|
* @since Envoy Client v0.1-beta
|
||||||
*/
|
*/
|
||||||
public GroupChat(User sender, Contact recipient) {
|
public GroupChat(User sender, Group recipient) {
|
||||||
super(recipient);
|
super(recipient);
|
||||||
this.sender = sender;
|
this.sender = sender;
|
||||||
}
|
}
|
||||||
@ -34,10 +34,13 @@ public final class GroupChat extends Chat {
|
|||||||
public void read(WriteProxy writeProxy) {
|
public void read(WriteProxy writeProxy) {
|
||||||
for (int i = messages.size() - 1; i >= 0; --i) {
|
for (int i = messages.size() - 1; i >= 0; --i) {
|
||||||
final GroupMessage gmsg = (GroupMessage) messages.get(i);
|
final GroupMessage gmsg = (GroupMessage) messages.get(i);
|
||||||
if (gmsg.getSenderID() != sender.getID()) if (gmsg.getMemberStatuses().get(sender.getID()) == MessageStatus.READ) break;
|
if (gmsg.getSenderID() != sender.getID())
|
||||||
|
if (gmsg.getMemberStatuses().get(sender.getID()) == MessageStatus.READ)
|
||||||
|
break;
|
||||||
else {
|
else {
|
||||||
gmsg.getMemberStatuses().replace(sender.getID(), MessageStatus.READ);
|
gmsg.getMemberStatuses().replace(sender.getID(), MessageStatus.READ);
|
||||||
writeProxy.writeMessageStatusChange(new GroupMessageStatusChange(gmsg.getID(), MessageStatus.READ, Instant.now(), sender.getID()));
|
writeProxy.writeMessageStatusChange(new GroupMessageStatusChange(gmsg.getID(),
|
||||||
|
MessageStatus.READ, Instant.now(), sender.getID()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
unreadAmount = 0;
|
unreadAmount = 0;
|
||||||
|
@ -1,35 +1,40 @@
|
|||||||
package envoy.client.data;
|
package envoy.client.data;
|
||||||
|
|
||||||
|
import static java.util.function.Predicate.not;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.nio.channels.*;
|
import java.nio.channels.*;
|
||||||
import java.nio.file.StandardOpenOption;
|
import java.nio.file.StandardOpenOption;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.logging.*;
|
import java.util.logging.*;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import javafx.application.Platform;
|
||||||
import javafx.collections.*;
|
import javafx.collections.*;
|
||||||
|
|
||||||
import envoy.client.event.*;
|
import dev.kske.eventbus.core.*;
|
||||||
|
import dev.kske.eventbus.core.Event;
|
||||||
|
|
||||||
import envoy.data.*;
|
import envoy.data.*;
|
||||||
import envoy.data.Message.MessageStatus;
|
import envoy.data.Message.MessageStatus;
|
||||||
import envoy.event.*;
|
import envoy.event.*;
|
||||||
|
import envoy.event.contact.*;
|
||||||
import envoy.exception.EnvoyException;
|
import envoy.exception.EnvoyException;
|
||||||
import envoy.util.*;
|
import envoy.util.*;
|
||||||
|
|
||||||
import dev.kske.eventbus.Event;
|
import envoy.client.event.*;
|
||||||
import dev.kske.eventbus.EventBus;
|
|
||||||
import dev.kske.eventbus.EventListener;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores information about the current {@link User} and their {@link Chat}s.
|
* Stores information about the current {@link User} and their {@link Chat}s. For message ID
|
||||||
* For message ID generation a {@link IDGenerator} is stored as well.
|
* generation a {@link IDGenerator} is stored as well.
|
||||||
* <p>
|
* <p>
|
||||||
* The managed objects are stored inside a folder in the local file system.
|
* The managed objects are stored inside a folder in the local file system.
|
||||||
*
|
*
|
||||||
* @author Kai S. K. Engelbart
|
* @author Kai S. K. Engelbart
|
||||||
* @since Envoy Client v0.3-alpha
|
* @since Envoy Client v0.3-alpha
|
||||||
*/
|
*/
|
||||||
public final class LocalDB implements EventListener {
|
public final class LocalDB {
|
||||||
|
|
||||||
// Data
|
// Data
|
||||||
private User user;
|
private User user;
|
||||||
@ -38,6 +43,7 @@ public final class LocalDB implements EventListener {
|
|||||||
private IDGenerator idGenerator;
|
private IDGenerator idGenerator;
|
||||||
private CacheMap cacheMap = new CacheMap();
|
private CacheMap cacheMap = new CacheMap();
|
||||||
private String authToken;
|
private String authToken;
|
||||||
|
private boolean contactsChanged;
|
||||||
|
|
||||||
// Auto save timer
|
// Auto save timer
|
||||||
private Timer autoSaver;
|
private Timer autoSaver;
|
||||||
@ -67,8 +73,11 @@ public final class LocalDB implements EventListener {
|
|||||||
EventBus.getInstance().registerListener(this);
|
EventBus.getInstance().registerListener(this);
|
||||||
|
|
||||||
// Ensure that the database directory exists
|
// Ensure that the database directory exists
|
||||||
if (!dbDir.exists()) dbDir.mkdirs();
|
if (!dbDir.exists())
|
||||||
else if (!dbDir.isDirectory()) throw new IOException(String.format("LocalDBDir '%s' is not a directory!", dbDir.getAbsolutePath()));
|
dbDir.mkdirs();
|
||||||
|
else if (!dbDir.isDirectory())
|
||||||
|
throw new IOException(
|
||||||
|
String.format("LocalDBDir '%s' is not a directory!", dbDir.getAbsolutePath()));
|
||||||
|
|
||||||
// Lock the directory
|
// Lock the directory
|
||||||
lock();
|
lock();
|
||||||
@ -87,8 +96,7 @@ public final class LocalDB implements EventListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensured that only one Envoy instance is using this local database by creating
|
* Ensured that only one Envoy instance is using this local database by creating a lock file.
|
||||||
* a lock file.
|
|
||||||
* The lock file is deleted on application exit.
|
* The lock file is deleted on application exit.
|
||||||
*
|
*
|
||||||
* @throws EnvoyException if the lock cannot by acquired
|
* @throws EnvoyException if the lock cannot by acquired
|
||||||
@ -97,17 +105,19 @@ public final class LocalDB implements EventListener {
|
|||||||
private synchronized void lock() throws EnvoyException {
|
private synchronized void lock() throws EnvoyException {
|
||||||
final var file = new File(dbDir, "instance.lock");
|
final var file = new File(dbDir, "instance.lock");
|
||||||
try {
|
try {
|
||||||
final var fc = FileChannel.open(file.toPath(), StandardOpenOption.CREATE, StandardOpenOption.WRITE);
|
final var fc = FileChannel.open(file.toPath(), StandardOpenOption.CREATE,
|
||||||
|
StandardOpenOption.WRITE);
|
||||||
instanceLock = fc.tryLock();
|
instanceLock = fc.tryLock();
|
||||||
if (instanceLock == null) throw new EnvoyException("Another Envoy instance is using this local database!");
|
if (instanceLock == null)
|
||||||
|
throw new EnvoyException("Another Envoy instance is using this local database!");
|
||||||
} catch (final IOException e) {
|
} catch (final IOException e) {
|
||||||
throw new EnvoyException("Could not create lock file!", e);
|
throw new EnvoyException("Could not create lock file!", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads the local user registry {@code users.db}, the id generator
|
* Loads the local user registry {@code users.db}, the id generator {@code id_gen.db} and last
|
||||||
* {@code id_gen.db} and last login file {@code last_login.db}.
|
* login file {@code last_login.db}.
|
||||||
*
|
*
|
||||||
* @since Envoy Client v0.2-beta
|
* @since Envoy Client v0.2-beta
|
||||||
*/
|
*/
|
||||||
@ -132,10 +142,46 @@ public final class LocalDB implements EventListener {
|
|||||||
* @since Envoy Client v0.3-alpha
|
* @since Envoy Client v0.3-alpha
|
||||||
*/
|
*/
|
||||||
public synchronized void loadUserData() throws ClassNotFoundException, IOException {
|
public synchronized void loadUserData() throws ClassNotFoundException, IOException {
|
||||||
if (user == null) throw new IllegalStateException("Client user is null, cannot initialize user storage");
|
if (user == null)
|
||||||
|
throw new IllegalStateException("Client user is null, cannot initialize user storage");
|
||||||
userFile = new File(dbDir, user.getID() + ".db");
|
userFile = new File(dbDir, user.getID() + ".db");
|
||||||
try (var in = new ObjectInputStream(new FileInputStream(userFile))) {
|
try (var in = new ObjectInputStream(new FileInputStream(userFile))) {
|
||||||
|
Chat.getTotalUnreadAmount().set(0);
|
||||||
chats = FXCollections.observableList((List<Chat>) in.readObject());
|
chats = FXCollections.observableList((List<Chat>) in.readObject());
|
||||||
|
|
||||||
|
// Some chats have changed and should not be overwritten by the saved values
|
||||||
|
if (contactsChanged) {
|
||||||
|
final var contacts = user.getContacts();
|
||||||
|
|
||||||
|
// Mark chats as disabled if a contact is no longer in this users contact list
|
||||||
|
final var changedUserChats = chats.stream()
|
||||||
|
.filter(not(chat -> contacts.contains(chat.getRecipient())))
|
||||||
|
.peek(chat -> {
|
||||||
|
chat.setDisabled(true);
|
||||||
|
logger.log(Level.INFO,
|
||||||
|
String.format("Deleted chat with %s.", chat.getRecipient()));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Also update groups with a different member count
|
||||||
|
final var changedGroupChats =
|
||||||
|
contacts.stream().filter(Group.class::isInstance).flatMap(group -> {
|
||||||
|
final var potentialChat = getChat(group.getID());
|
||||||
|
if (potentialChat.isEmpty())
|
||||||
|
return Stream.empty();
|
||||||
|
final var chat = potentialChat.get();
|
||||||
|
if (group.getContacts().size() != chat.getRecipient().getContacts()
|
||||||
|
.size()) {
|
||||||
|
logger.log(Level.INFO, "Removed one (or more) members from " + group);
|
||||||
|
return Stream.of(chat);
|
||||||
|
} else
|
||||||
|
return Stream.empty();
|
||||||
|
});
|
||||||
|
Stream.concat(changedUserChats, changedGroupChats)
|
||||||
|
.forEach(chat -> chats.set(chats.indexOf(chat), chat));
|
||||||
|
|
||||||
|
// loadUserData can get called two (or more?) times during application lifecycle
|
||||||
|
contactsChanged = false;
|
||||||
|
}
|
||||||
cacheMap = (CacheMap) in.readObject();
|
cacheMap = (CacheMap) in.readObject();
|
||||||
lastSync = (Instant) in.readObject();
|
lastSync = (Instant) in.readObject();
|
||||||
} finally {
|
} finally {
|
||||||
@ -144,30 +190,34 @@ public final class LocalDB implements EventListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Synchronizes the contact list of the client user with the chat and user
|
* Synchronizes the contact list of the client user with the chat and user storage.
|
||||||
* storage.
|
|
||||||
*
|
*
|
||||||
* @since Envoy Client v0.1-beta
|
* @since Envoy Client v0.1-beta
|
||||||
*/
|
*/
|
||||||
private void synchronize() {
|
private void synchronize() {
|
||||||
user.getContacts().stream().filter(u -> u instanceof User && !users.containsKey(u.getName())).forEach(u -> users.put(u.getName(), (User) u));
|
user.getContacts().stream()
|
||||||
|
.filter(u -> u instanceof User && !users.containsKey(u.getName()))
|
||||||
|
.forEach(u -> users.put(u.getName(), (User) u));
|
||||||
users.put(user.getName(), user);
|
users.put(user.getName(), user);
|
||||||
|
|
||||||
// Synchronize user status data
|
// Synchronize user status data
|
||||||
for (final var contact : users.values())
|
for (final var contact : user.getContacts())
|
||||||
if (contact instanceof User) getChat(contact.getID()).ifPresent(chat -> { ((User) chat.getRecipient()).setStatus(contact.getStatus()); });
|
if (contact instanceof User)
|
||||||
|
getChat(contact.getID()).ifPresent(chat -> {
|
||||||
|
((User) chat.getRecipient()).setStatus(((User) contact).getStatus());
|
||||||
|
});
|
||||||
|
|
||||||
// Create missing chats
|
// Create missing chats
|
||||||
user.getContacts()
|
user.getContacts()
|
||||||
.stream()
|
.stream()
|
||||||
.filter(c -> !c.equals(user) && getChat(c.getID()).isEmpty())
|
.filter(c -> !c.equals(user) && getChat(c.getID()).isEmpty())
|
||||||
.map(c -> c instanceof User ? new Chat(c) : new GroupChat(user, c))
|
.map(c -> c instanceof User ? new Chat(c) : new GroupChat(user, (Group) c))
|
||||||
.forEach(chats::add);
|
.forEach(chats::add);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes a timer that automatically saves this local database after a
|
* Initializes a timer that automatically saves this local database after a period of time
|
||||||
* period of time specified in the settings.
|
* specified in the settings.
|
||||||
*
|
*
|
||||||
* @since Envoy Client v0.2-beta
|
* @since Envoy Client v0.2-beta
|
||||||
*/
|
*/
|
||||||
@ -182,68 +232,148 @@ public final class LocalDB implements EventListener {
|
|||||||
autoSaver.schedule(new TimerTask() {
|
autoSaver.schedule(new TimerTask() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() { save(); }
|
public void run() {
|
||||||
|
save();
|
||||||
|
}
|
||||||
}, 2000, ClientConfig.getInstance().getLocalDBSaveInterval() * 60000);
|
}, 2000, ClientConfig.getInstance().getLocalDBSaveInterval() * 60000);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores all users. If the client user is specified, their chats will be stored
|
* Stores all users. If the client user is specified, their chats will be stored as well. The
|
||||||
* as well. The message id generator will also be saved if present.
|
* message id generator will also be saved if present.
|
||||||
*
|
*
|
||||||
* @throws IOException if the saving process failed
|
* @throws IOException if the saving process failed
|
||||||
* @since Envoy Client v0.3-alpha
|
* @since Envoy Client v0.3-alpha
|
||||||
*/
|
*/
|
||||||
@Event(eventType = EnvoyCloseEvent.class, priority = 1000)
|
@Event(EnvoyCloseEvent.class)
|
||||||
|
@Priority(500)
|
||||||
private synchronized void save() {
|
private synchronized void save() {
|
||||||
EnvoyLog.getLogger(LocalDB.class).log(Level.INFO, "Saving local database...");
|
|
||||||
|
// Stop saving if this account has been deleted
|
||||||
|
if (userFile == null)
|
||||||
|
return;
|
||||||
|
EnvoyLog.getLogger(LocalDB.class).log(Level.FINER, "Saving local database...");
|
||||||
|
|
||||||
// Save users
|
// Save users
|
||||||
try {
|
try {
|
||||||
SerializationUtils.write(usersFile, users);
|
SerializationUtils.write(usersFile, users);
|
||||||
|
|
||||||
// Save user data and last sync time stamp
|
// Save user data and last sync time stamp
|
||||||
if (user != null) SerializationUtils
|
if (user != null)
|
||||||
.write(userFile, new ArrayList<>(chats), cacheMap, Context.getInstance().getClient().isOnline() ? Instant.now() : lastSync);
|
SerializationUtils
|
||||||
|
.write(userFile, new ArrayList<>(chats), cacheMap,
|
||||||
|
Context.getInstance().getClient().isOnline() ? Instant.now() : lastSync);
|
||||||
|
|
||||||
// Save last login information
|
// Save last login information
|
||||||
if (authToken != null) SerializationUtils.write(lastLoginFile, user, authToken);
|
if (authToken != null)
|
||||||
|
SerializationUtils.write(lastLoginFile, user, authToken);
|
||||||
|
|
||||||
// Save ID generator
|
// Save ID generator
|
||||||
if (hasIDGenerator()) SerializationUtils.write(idGeneratorFile, idGenerator);
|
if (hasIDGenerator())
|
||||||
|
SerializationUtils.write(idGeneratorFile, idGenerator);
|
||||||
} catch (final IOException e) {
|
} catch (final IOException e) {
|
||||||
EnvoyLog.getLogger(LocalDB.class).log(Level.SEVERE, "Unable to save local database: ", e);
|
EnvoyLog.getLogger(LocalDB.class).log(Level.SEVERE, "Unable to save local database: ",
|
||||||
|
e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Event(priority = 150)
|
/**
|
||||||
private void onMessage(Message msg) { if (msg.getStatus() == MessageStatus.SENT) msg.nextStatus(); }
|
* Deletes any local remnant of this user.
|
||||||
|
*
|
||||||
|
* @since Envoy Client v0.3-beta
|
||||||
|
*/
|
||||||
|
public void delete() {
|
||||||
|
try {
|
||||||
|
|
||||||
@Event(priority = 150)
|
// Save ID generator - can be used for other users in that db
|
||||||
|
if (hasIDGenerator())
|
||||||
|
SerializationUtils.write(idGeneratorFile, idGenerator);
|
||||||
|
} catch (final IOException e) {
|
||||||
|
EnvoyLog.getLogger(LocalDB.class).log(Level.SEVERE, "Unable to save local database: ",
|
||||||
|
e);
|
||||||
|
}
|
||||||
|
if (lastLoginFile != null)
|
||||||
|
lastLoginFile.delete();
|
||||||
|
userFile.delete();
|
||||||
|
users.remove(user.getName());
|
||||||
|
userFile = null;
|
||||||
|
onLogout();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Event
|
||||||
|
@Priority(500)
|
||||||
|
private void onMessage(Message msg) {
|
||||||
|
if (msg.getStatus() == MessageStatus.SENT)
|
||||||
|
msg.nextStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Event
|
||||||
|
@Priority(500)
|
||||||
private void onGroupMessage(GroupMessage msg) {
|
private void onGroupMessage(GroupMessage msg) {
|
||||||
// TODO: Cancel event once EventBus is updated
|
// TODO: Cancel event once EventBus is updated
|
||||||
if (msg.getStatus() == MessageStatus.WAITING || msg.getStatus() == MessageStatus.READ)
|
if (msg.getStatus() == MessageStatus.WAITING || msg.getStatus() == MessageStatus.READ)
|
||||||
logger.warning("The groupMessage has the unexpected status " + msg.getStatus());
|
logger.warning("The groupMessage has the unexpected status " + msg.getStatus());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Event(priority = 150)
|
@Event
|
||||||
private void onMessageStatusChange(MessageStatusChange evt) { getMessage(evt.getID()).ifPresent(msg -> msg.setStatus(evt.get())); }
|
@Priority(500)
|
||||||
|
private void onMessageStatusChange(MessageStatusChange evt) {
|
||||||
|
getMessage(evt.getID()).ifPresent(msg -> msg.setStatus(evt.get()));
|
||||||
|
}
|
||||||
|
|
||||||
@Event(priority = 150)
|
@Event
|
||||||
|
@Priority(500)
|
||||||
private void onGroupMessageStatusChange(GroupMessageStatusChange evt) {
|
private void onGroupMessageStatusChange(GroupMessageStatusChange evt) {
|
||||||
this.<GroupMessage>getMessage(evt.getID()).ifPresent(msg -> msg.getMemberStatuses().replace(evt.getMemberID(), evt.get()));
|
this.<GroupMessage>getMessage(evt.getID())
|
||||||
|
.ifPresent(msg -> msg.getMemberStatuses().replace(evt.getMemberID(), evt.get()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Event(priority = 150)
|
@Event
|
||||||
|
@Priority(500)
|
||||||
private void onUserStatusChange(UserStatusChange evt) {
|
private void onUserStatusChange(UserStatusChange evt) {
|
||||||
this.getChat(evt.getID()).map(Chat::getRecipient).map(User.class::cast).ifPresent(u -> u.setStatus(evt.get()));
|
getChat(evt.getID()).map(Chat::getRecipient).map(User.class::cast)
|
||||||
|
.ifPresent(u -> u.setStatus(evt.get()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Event(priority = 150)
|
@Event
|
||||||
private void onGroupResize(GroupResize evt) { getChat(evt.getGroupID()).map(Chat::getRecipient).map(Group.class::cast).ifPresent(evt::apply); }
|
@Priority(500)
|
||||||
|
private void onUserOperation(UserOperation operation) {
|
||||||
|
final var eventUser = operation.get();
|
||||||
|
switch (operation.getOperationType()) {
|
||||||
|
case ADD:
|
||||||
|
Platform.runLater(() -> chats.add(0, new Chat(eventUser)));
|
||||||
|
break;
|
||||||
|
case REMOVE:
|
||||||
|
getChat(eventUser.getID()).ifPresent(chat -> chat.setDisabled(true));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Event(priority = 150)
|
@Event
|
||||||
|
private void onGroupCreationResult(GroupCreationResult evt) {
|
||||||
|
final var newGroup = evt.get();
|
||||||
|
|
||||||
|
// The group creation was not successful
|
||||||
|
if (newGroup == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// The group was successfully created
|
||||||
|
else
|
||||||
|
Platform.runLater(() -> chats.add(new GroupChat(user, newGroup)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Event
|
||||||
|
@Priority(500)
|
||||||
|
private void onGroupResize(GroupResize evt) {
|
||||||
|
getChat(evt.getGroupID()).map(Chat::getRecipient).map(Group.class::cast)
|
||||||
|
.ifPresent(evt::apply);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Event
|
||||||
|
@Priority(500)
|
||||||
private void onNameChange(NameChange evt) {
|
private void onNameChange(NameChange evt) {
|
||||||
chats.stream().map(Chat::getRecipient).filter(c -> c.getID() == evt.getID()).findAny().ifPresent(c -> c.setName(evt.get()));
|
chats.stream().map(Chat::getRecipient).filter(c -> c.getID() == evt.getID()).findAny()
|
||||||
|
.ifPresent(c -> c.setName(evt.get()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -253,14 +383,17 @@ public final class LocalDB implements EventListener {
|
|||||||
* @since Envoy Client v0.2-beta
|
* @since Envoy Client v0.2-beta
|
||||||
*/
|
*/
|
||||||
@Event
|
@Event
|
||||||
private void onNewAuthToken(NewAuthToken evt) { authToken = evt.get(); }
|
private void onNewAuthToken(NewAuthToken evt) {
|
||||||
|
authToken = evt.get();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes all associations to the current user.
|
* Deletes all associations to the current user.
|
||||||
*
|
*
|
||||||
* @since Envoy Client v0.2-beta
|
* @since Envoy Client v0.2-beta
|
||||||
*/
|
*/
|
||||||
@Event(eventType = Logout.class, priority = 100)
|
@Event(Logout.class)
|
||||||
|
@Priority(50)
|
||||||
private void onLogout() {
|
private void onLogout() {
|
||||||
autoSaver.cancel();
|
autoSaver.cancel();
|
||||||
autoSaveRestart = true;
|
autoSaveRestart = true;
|
||||||
@ -274,8 +407,53 @@ public final class LocalDB implements EventListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return a {@code Map<String, User>} of all users stored locally with their
|
* Deletes the message with the given ID, if present.
|
||||||
* user names as keys
|
*
|
||||||
|
* @param message the event that was
|
||||||
|
* @since Envoy Client v0.3-beta
|
||||||
|
*/
|
||||||
|
@Event
|
||||||
|
private void onMessageDeletion(MessageDeletion message) {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
|
||||||
|
// We suppose that messages have unique IDs, hence the search can be stopped
|
||||||
|
// once a message was removed
|
||||||
|
final var messageID = message.get();
|
||||||
|
for (final var chat : chats)
|
||||||
|
if (chat.remove(messageID))
|
||||||
|
break;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Event
|
||||||
|
@Priority(500)
|
||||||
|
private void onOwnStatusChange(OwnStatusChange statusChange) {
|
||||||
|
user.setStatus(statusChange.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Event(ContactsChangedSinceLastLogin.class)
|
||||||
|
@Priority(500)
|
||||||
|
private void onContactsChangedSinceLastLogin() {
|
||||||
|
contactsChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Event
|
||||||
|
@Priority(500)
|
||||||
|
private void onContactDisabled(ContactDisabled event) {
|
||||||
|
getChat(event.get().getID()).ifPresent(chat -> chat.setDisabled(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Event
|
||||||
|
@Priority(500)
|
||||||
|
private void onAccountDeletion(AccountDeletion deletion) {
|
||||||
|
if (user.getID() == deletion.get())
|
||||||
|
logger.log(Level.WARNING,
|
||||||
|
"I have been informed by the server that I have been deleted without even knowing it...");
|
||||||
|
getChat(deletion.get()).ifPresent(chat -> chat.setDisabled(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return a {@code Map<String, User>} of all users stored locally with their user names as keys
|
||||||
* @since Envoy Client v0.2-alpha
|
* @since Envoy Client v0.2-alpha
|
||||||
*/
|
*/
|
||||||
public Map<String, User> getUsers() { return users; }
|
public Map<String, User> getUsers() { return users; }
|
||||||
@ -288,7 +466,8 @@ public final class LocalDB implements EventListener {
|
|||||||
* @since Envoy Client v0.1-beta
|
* @since Envoy Client v0.1-beta
|
||||||
*/
|
*/
|
||||||
public <T extends Message> Optional<T> getMessage(long id) {
|
public <T extends Message> Optional<T> getMessage(long id) {
|
||||||
return (Optional<T>) chats.stream().map(Chat::getMessages).flatMap(List::stream).filter(m -> m.getID() == id).findAny();
|
return (Optional<T>) chats.stream().map(Chat::getMessages).flatMap(List::stream)
|
||||||
|
.filter(m -> m.getID() == id).findAny();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -298,11 +477,12 @@ public final class LocalDB implements EventListener {
|
|||||||
* @return an optional containing the chat
|
* @return an optional containing the chat
|
||||||
* @since Envoy Client v0.1-beta
|
* @since Envoy Client v0.1-beta
|
||||||
*/
|
*/
|
||||||
public Optional<Chat> getChat(long recipientID) { return chats.stream().filter(c -> c.getRecipient().getID() == recipientID).findAny(); }
|
public Optional<Chat> getChat(long recipientID) {
|
||||||
|
return chats.stream().filter(c -> c.getRecipient().getID() == recipientID).findAny();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return all saved {@link Chat} objects that list the client user as the
|
* @return all saved {@link Chat} objects that list the client user as the sender
|
||||||
* sender
|
|
||||||
* @since Envoy Client v0.1-alpha
|
* @since Envoy Client v0.1-alpha
|
||||||
**/
|
**/
|
||||||
public ObservableList<Chat> getChats() { return chats; }
|
public ObservableList<Chat> getChats() { return chats; }
|
||||||
@ -329,14 +509,17 @@ public final class LocalDB implements EventListener {
|
|||||||
* @param idGenerator the message ID generator to set
|
* @param idGenerator the message ID generator to set
|
||||||
* @since Envoy Client v0.3-alpha
|
* @since Envoy Client v0.3-alpha
|
||||||
*/
|
*/
|
||||||
@Event(priority = 150)
|
@Event
|
||||||
|
@Priority(150)
|
||||||
public void setIDGenerator(IDGenerator idGenerator) { this.idGenerator = idGenerator; }
|
public void setIDGenerator(IDGenerator idGenerator) { this.idGenerator = idGenerator; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return {@code true} if an {@link IDGenerator} is present
|
* @return {@code true} if an {@link IDGenerator} is present
|
||||||
* @since Envoy Client v0.3-alpha
|
* @since Envoy Client v0.3-alpha
|
||||||
*/
|
*/
|
||||||
public boolean hasIDGenerator() { return idGenerator != null; }
|
public boolean hasIDGenerator() {
|
||||||
|
return idGenerator != null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the cache map for messages and message status changes
|
* @return the cache map for messages and message status changes
|
||||||
|
@ -5,23 +5,22 @@ import java.util.*;
|
|||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.prefs.Preferences;
|
import java.util.prefs.Preferences;
|
||||||
|
|
||||||
import envoy.client.event.EnvoyCloseEvent;
|
import dev.kske.eventbus.core.*;
|
||||||
|
|
||||||
import envoy.util.*;
|
import envoy.util.*;
|
||||||
|
|
||||||
import dev.kske.eventbus.*;
|
import envoy.client.event.EnvoyCloseEvent;
|
||||||
import dev.kske.eventbus.EventListener;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages all application settings, which are different objects that can be
|
* Manages all application settings, which are different objects that can be changed during runtime
|
||||||
* changed during runtime and serialized them by using either the file system or
|
* and serialized them by using either the file system or the {@link Preferences} API.
|
||||||
* the {@link Preferences} API.
|
|
||||||
*
|
*
|
||||||
* @author Leon Hofmeister
|
* @author Leon Hofmeister
|
||||||
* @author Maximilian Käfer
|
* @author Maximilian Käfer
|
||||||
* @author Kai S. K. Engelbart
|
* @author Kai S. K. Engelbart
|
||||||
* @since Envoy Client v0.2-alpha
|
* @since Envoy Client v0.2-alpha
|
||||||
*/
|
*/
|
||||||
public final class Settings implements EventListener {
|
public final class Settings {
|
||||||
|
|
||||||
// Actual settings accessible by the rest of the application
|
// Actual settings accessible by the rest of the application
|
||||||
private Map<String, SettingsItem<?>> items;
|
private Map<String, SettingsItem<?>> items;
|
||||||
@ -29,7 +28,8 @@ public final class Settings implements EventListener {
|
|||||||
/**
|
/**
|
||||||
* Settings are stored in this file.
|
* Settings are stored in this file.
|
||||||
*/
|
*/
|
||||||
private static final File settingsFile = new File(ClientConfig.getInstance().getHomeDirectory(), "settings.ser");
|
private static final File settingsFile =
|
||||||
|
new File(ClientConfig.getInstance().getHomeDirectory(), "settings.ser");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Singleton instance of this class.
|
* Singleton instance of this class.
|
||||||
@ -37,8 +37,8 @@ public final class Settings implements EventListener {
|
|||||||
private static Settings settings = new Settings();
|
private static Settings settings = new Settings();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The way to instantiate the settings. Is set to private to deny other
|
* The way to instantiate the settings. Is set to private to deny other instances of that
|
||||||
* instances of that object.
|
* object.
|
||||||
*
|
*
|
||||||
* @since Envoy Client v0.2-alpha
|
* @since Envoy Client v0.2-alpha
|
||||||
*/
|
*/
|
||||||
@ -68,7 +68,7 @@ public final class Settings implements EventListener {
|
|||||||
* @throws IOException if an error occurs while saving the themes
|
* @throws IOException if an error occurs while saving the themes
|
||||||
* @since Envoy Client v0.2-alpha
|
* @since Envoy Client v0.2-alpha
|
||||||
*/
|
*/
|
||||||
@Event(eventType = EnvoyCloseEvent.class, priority = 900)
|
@Event(EnvoyCloseEvent.class)
|
||||||
private void save() {
|
private void save() {
|
||||||
EnvoyLog.getLogger(Settings.class).log(Level.INFO, "Saving settings...");
|
EnvoyLog.getLogger(Settings.class).log(Level.INFO, "Saving settings...");
|
||||||
|
|
||||||
@ -76,20 +76,27 @@ public final class Settings implements EventListener {
|
|||||||
try {
|
try {
|
||||||
SerializationUtils.write(settingsFile, items);
|
SerializationUtils.write(settingsFile, items);
|
||||||
} catch (final IOException e) {
|
} catch (final IOException e) {
|
||||||
EnvoyLog.getLogger(Settings.class).log(Level.SEVERE, "Unable to save settings file: ", e);
|
EnvoyLog.getLogger(Settings.class).log(Level.SEVERE, "Unable to save settings file: ",
|
||||||
|
e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void supplementDefaults() {
|
private void supplementDefaults() {
|
||||||
items.putIfAbsent("enterToSend", new SettingsItem<>(true, "Enter to send", "Sends a message by pressing the enter key."));
|
items.putIfAbsent("enterToSend", new SettingsItem<>(true, "Enter to send",
|
||||||
items.putIfAbsent("hideOnClose", new SettingsItem<>(false, "Hide on close", "Hides the chat window when it is closed."));
|
"Sends a message by pressing the enter key."));
|
||||||
items.putIfAbsent("currentTheme", new SettingsItem<>("dark", "Current Theme Name", "The name of the currently selected theme."));
|
items.putIfAbsent("hideOnClose",
|
||||||
|
new SettingsItem<>(false, "Hide on close", "Hides the chat window when it is closed."));
|
||||||
|
items.putIfAbsent("currentTheme", new SettingsItem<>("dark", "Current Theme Name",
|
||||||
|
"The name of the currently selected theme."));
|
||||||
items.putIfAbsent("downloadLocation",
|
items.putIfAbsent("downloadLocation",
|
||||||
new SettingsItem<>(new File(System.getProperty("user.home") + "/Downloads/"), "Download location",
|
new SettingsItem<>(new File(System.getProperty("user.home") + "/Downloads/"),
|
||||||
|
"Download location",
|
||||||
"The location where files will be saved to"));
|
"The location where files will be saved to"));
|
||||||
items.putIfAbsent("autoSaveDownloads", new SettingsItem<>(false, "Save without asking?", "Should downloads be saved without asking?"));
|
items.putIfAbsent("autoSaveDownloads", new SettingsItem<>(false, "Save without asking?",
|
||||||
|
"Should downloads be saved without asking?"));
|
||||||
items.putIfAbsent("askForConfirmation",
|
items.putIfAbsent("askForConfirmation",
|
||||||
new SettingsItem<>(true, "Ask for confirmation", "Will ask for confirmation before doing certain things"));
|
new SettingsItem<>(true, "Ask for confirmation",
|
||||||
|
"Will ask for confirmation before doing certain things"));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -104,7 +111,9 @@ public final class Settings implements EventListener {
|
|||||||
* @param themeName the name to set
|
* @param themeName the name to set
|
||||||
* @since Envoy Client v0.2-alpha
|
* @since Envoy Client v0.2-alpha
|
||||||
*/
|
*/
|
||||||
public void setCurrentTheme(String themeName) { ((SettingsItem<String>) items.get("currentTheme")).set(themeName); }
|
public void setCurrentTheme(String themeName) {
|
||||||
|
((SettingsItem<String>) items.get("currentTheme")).set(themeName);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return true if the currently used theme is one of the default themes
|
* @return true if the currently used theme is one of the default themes
|
||||||
@ -116,9 +125,8 @@ public final class Settings implements EventListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return {@code true}, if pressing the {@code Enter} key suffices to send a
|
* @return {@code true}, if pressing the {@code Enter} key suffices to send a message. Otherwise
|
||||||
* message. Otherwise it has to be pressed in conjunction with the
|
* it has to be pressed in conjunction with the {@code Control} key.
|
||||||
* {@code Control} key.
|
|
||||||
* @since Envoy Client v0.2-alpha
|
* @since Envoy Client v0.2-alpha
|
||||||
*/
|
*/
|
||||||
public Boolean isEnterToSend() { return (Boolean) items.get("enterToSend").get(); }
|
public Boolean isEnterToSend() { return (Boolean) items.get("enterToSend").get(); }
|
||||||
@ -126,26 +134,27 @@ public final class Settings implements EventListener {
|
|||||||
/**
|
/**
|
||||||
* Changes the keystrokes performed by the user to send a message.
|
* Changes the keystrokes performed by the user to send a message.
|
||||||
*
|
*
|
||||||
* @param enterToSend If set to {@code true} a message can be sent by pressing
|
* @param enterToSend If set to {@code true} a message can be sent by pressing the {@code Enter}
|
||||||
* the {@code Enter} key. Otherwise it has to be pressed in
|
* key. Otherwise it has to be pressed in conjunction with the
|
||||||
* conjunction with the {@code Control} key.
|
* {@code Control} key.
|
||||||
* @since Envoy Client v0.2-alpha
|
* @since Envoy Client v0.2-alpha
|
||||||
*/
|
*/
|
||||||
public void setEnterToSend(boolean enterToSend) { ((SettingsItem<Boolean>) items.get("enterToSend")).set(enterToSend); }
|
public void setEnterToSend(boolean enterToSend) {
|
||||||
|
((SettingsItem<Boolean>) items.get("enterToSend")).set(enterToSend);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return whether Envoy will prompt a dialogue before saving an
|
* @return whether Envoy will prompt a dialogue before saving an {@link envoy.data.Attachment}
|
||||||
* {@link envoy.data.Attachment}
|
|
||||||
* @since Envoy Client v0.2-beta
|
* @since Envoy Client v0.2-beta
|
||||||
*/
|
*/
|
||||||
public Boolean isDownloadSavedWithoutAsking() { return (Boolean) items.get("autoSaveDownloads").get(); }
|
public Boolean isDownloadSavedWithoutAsking() {
|
||||||
|
return (Boolean) items.get("autoSaveDownloads").get();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets whether Envoy will prompt a dialogue before saving an
|
* Sets whether Envoy will prompt a dialogue before saving an {@link envoy.data.Attachment}.
|
||||||
* {@link envoy.data.Attachment}.
|
|
||||||
*
|
*
|
||||||
* @param autosaveDownload whether a download should be saved without asking
|
* @param autosaveDownload whether a download should be saved without asking before
|
||||||
* before
|
|
||||||
* @since Envoy Client v0.2-beta
|
* @since Envoy Client v0.2-beta
|
||||||
*/
|
*/
|
||||||
public void setDownloadSavedWithoutAsking(boolean autosaveDownload) {
|
public void setDownloadSavedWithoutAsking(boolean autosaveDownload) {
|
||||||
@ -164,7 +173,9 @@ public final class Settings implements EventListener {
|
|||||||
* @param downloadLocation the path to set
|
* @param downloadLocation the path to set
|
||||||
* @since Envoy Client v0.2-beta
|
* @since Envoy Client v0.2-beta
|
||||||
*/
|
*/
|
||||||
public void setDownloadLocation(File downloadLocation) { ((SettingsItem<File>) items.get("downloadLocation")).set(downloadLocation); }
|
public void setDownloadLocation(File downloadLocation) {
|
||||||
|
((SettingsItem<File>) items.get("downloadLocation")).set(downloadLocation);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the current on close mode.
|
* @return the current on close mode.
|
||||||
@ -178,21 +189,24 @@ public final class Settings implements EventListener {
|
|||||||
* @param hideOnClose whether the application should be minimized on close
|
* @param hideOnClose whether the application should be minimized on close
|
||||||
* @since Envoy Client v0.3-alpha
|
* @since Envoy Client v0.3-alpha
|
||||||
*/
|
*/
|
||||||
public void setHideOnClose(boolean hideOnClose) { ((SettingsItem<Boolean>) items.get("hideOnClose")).set(hideOnClose); }
|
public void setHideOnClose(boolean hideOnClose) {
|
||||||
|
((SettingsItem<Boolean>) items.get("hideOnClose")).set(hideOnClose);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return whether a confirmation dialog should be displayed before certain
|
* @return whether a confirmation dialog should be displayed before certain actions
|
||||||
* actions
|
|
||||||
* @since Envoy Client v0.2-alpha
|
* @since Envoy Client v0.2-alpha
|
||||||
*/
|
*/
|
||||||
public Boolean isAskForConfirmation() { return (Boolean) items.get("askForConfirmation").get(); }
|
public Boolean isAskForConfirmation() {
|
||||||
|
return (Boolean) items.get("askForConfirmation").get();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Changes the behavior of calling certain functionality by displaying a
|
* Changes the behavior of calling certain functionality by displaying a confirmation dialog
|
||||||
* confirmation dialog before executing it.
|
* before executing it.
|
||||||
*
|
*
|
||||||
* @param askForConfirmation whether confirmation dialogs should be displayed
|
* @param askForConfirmation whether confirmation dialogs should be displayed before certain
|
||||||
* before certain actions
|
* actions
|
||||||
* @since Envoy Client v0.2-alpha
|
* @since Envoy Client v0.2-alpha
|
||||||
*/
|
*/
|
||||||
public void setAskForConfirmation(boolean askForConfirmation) {
|
public void setAskForConfirmation(boolean askForConfirmation) {
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
package envoy.client.data;
|
package envoy.client.data;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
import javax.swing.JComponent;
|
import javax.swing.JComponent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encapsulates a persistent value that is directly or indirectly mutable by the
|
* Encapsulates a persistent value that is directly or indirectly mutable by the user.
|
||||||
* user.
|
|
||||||
*
|
*
|
||||||
* @param <T> the type of this {@link SettingsItem}'s value
|
* @param <T> the type of this {@link SettingsItem}'s value
|
||||||
* @author Kai S. K. Engelbart
|
* @author Kai S. K. Engelbart
|
||||||
@ -18,14 +16,11 @@ public final class SettingsItem<T> implements Serializable {
|
|||||||
private T value;
|
private T value;
|
||||||
private String userFriendlyName, description;
|
private String userFriendlyName, description;
|
||||||
|
|
||||||
private transient Consumer<T> changeHandler;
|
|
||||||
|
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes a {@link SettingsItem}. The default value's class will be mapped
|
* Initializes a {@link SettingsItem}. The default value's class will be mapped to a
|
||||||
* to a {@link JComponent} that can be used to display this {@link SettingsItem}
|
* {@link JComponent} that can be used to display this {@link SettingsItem} to the user.
|
||||||
* to the user.
|
|
||||||
*
|
*
|
||||||
* @param value the default value
|
* @param value the default value
|
||||||
* @param userFriendlyName the user friendly name (short)
|
* @param userFriendlyName the user friendly name (short)
|
||||||
@ -42,17 +37,18 @@ public final class SettingsItem<T> implements Serializable {
|
|||||||
* @return the value
|
* @return the value
|
||||||
* @since Envoy Client v0.3-alpha
|
* @since Envoy Client v0.3-alpha
|
||||||
*/
|
*/
|
||||||
public T get() { return value; }
|
public T get() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Changes the value of this {@link SettingsItem}. If a {@code ChangeHandler} if
|
* Changes the value of this {@link SettingsItem}. If a {@code ChangeHandler} if defined, it
|
||||||
* defined, it will be invoked with this value.
|
* will be invoked with this value.
|
||||||
*
|
*
|
||||||
* @param value the value to set
|
* @param value the value to set
|
||||||
* @since Envoy Client v0.3-alpha
|
* @since Envoy Client v0.3-alpha
|
||||||
*/
|
*/
|
||||||
public void set(T value) {
|
public void set(T value) {
|
||||||
if (changeHandler != null && value != this.value) changeHandler.accept(value);
|
|
||||||
this.value = value;
|
this.value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,7 +62,9 @@ public final class SettingsItem<T> implements Serializable {
|
|||||||
* @param userFriendlyName the userFriendlyName to set
|
* @param userFriendlyName the userFriendlyName to set
|
||||||
* @since Envoy Client v0.3-alpha
|
* @since Envoy Client v0.3-alpha
|
||||||
*/
|
*/
|
||||||
public void setUserFriendlyName(String userFriendlyName) { this.userFriendlyName = userFriendlyName; }
|
public void setUserFriendlyName(String userFriendlyName) {
|
||||||
|
this.userFriendlyName = userFriendlyName;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the description
|
* @return the description
|
||||||
@ -79,17 +77,4 @@ public final class SettingsItem<T> implements Serializable {
|
|||||||
* @since Envoy Client v0.3-alpha
|
* @since Envoy Client v0.3-alpha
|
||||||
*/
|
*/
|
||||||
public void setDescription(String description) { this.description = description; }
|
public void setDescription(String description) { this.description = description; }
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets a {@code ChangeHandler} for this {@link SettingsItem}. It will be
|
|
||||||
* invoked with the current value once during the registration and every time
|
|
||||||
* when the value changes.
|
|
||||||
*
|
|
||||||
* @param changeHandler the changeHandler to set
|
|
||||||
* @since Envoy Client v0.3-alpha
|
|
||||||
*/
|
|
||||||
public void setChangeHandler(Consumer<T> changeHandler) {
|
|
||||||
this.changeHandler = changeHandler;
|
|
||||||
changeHandler.accept(value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,9 @@ public final class AudioPlayer {
|
|||||||
*
|
*
|
||||||
* @since Envoy Client v0.1-beta
|
* @since Envoy Client v0.1-beta
|
||||||
*/
|
*/
|
||||||
public AudioPlayer() { this(AudioRecorder.DEFAULT_AUDIO_FORMAT); }
|
public AudioPlayer() {
|
||||||
|
this(AudioRecorder.DEFAULT_AUDIO_FORMAT);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the player with a given audio format.
|
* Initializes the player with a given audio format.
|
||||||
|
@ -20,7 +20,8 @@ public final class AudioRecorder {
|
|||||||
*
|
*
|
||||||
* @since Envoy Client v0.1-beta
|
* @since Envoy Client v0.1-beta
|
||||||
*/
|
*/
|
||||||
public static final AudioFormat DEFAULT_AUDIO_FORMAT = new AudioFormat(16000, 16, 1, true, false);
|
public static final AudioFormat DEFAULT_AUDIO_FORMAT =
|
||||||
|
new AudioFormat(16000, 16, 1, true, false);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The format in which audio files will be saved.
|
* The format in which audio files will be saved.
|
||||||
@ -38,7 +39,9 @@ public final class AudioRecorder {
|
|||||||
*
|
*
|
||||||
* @since Envoy Client v0.1-beta
|
* @since Envoy Client v0.1-beta
|
||||||
*/
|
*/
|
||||||
public AudioRecorder() { this(DEFAULT_AUDIO_FORMAT); }
|
public AudioRecorder() {
|
||||||
|
this(DEFAULT_AUDIO_FORMAT);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the recorder with a given audio format.
|
* Initializes the recorder with a given audio format.
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
package envoy.client.data.commands;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This interface defines an action that should be performed when a system command gets called.
|
||||||
|
*
|
||||||
|
* @author Leon Hofmeister
|
||||||
|
* @since Envoy Client v0.2-beta
|
||||||
|
*/
|
||||||
|
public interface Callable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs the instance specific action when a {@link SystemCommand} has been called.
|
||||||
|
*
|
||||||
|
* @param arguments the arguments that should be passed to the {@link SystemCommand}
|
||||||
|
* @since Envoy Client v0.2-beta
|
||||||
|
*/
|
||||||
|
void call(List<String> arguments);
|
||||||
|
}
|
@ -1,30 +0,0 @@
|
|||||||
package envoy.client.data.commands;
|
|
||||||
|
|
||||||
import java.util.function.Supplier;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This interface defines an action that should be performed when a system
|
|
||||||
* command gets called.
|
|
||||||
*
|
|
||||||
* @author Leon Hofmeister
|
|
||||||
* @since Envoy Client v0.2-beta
|
|
||||||
*/
|
|
||||||
public interface OnCall {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Performs class specific actions when a {@link SystemCommand} has been called.
|
|
||||||
*
|
|
||||||
* @since Envoy Client v0.2-beta
|
|
||||||
*/
|
|
||||||
void onCall();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Performs actions that can only be performed by classes that are not
|
|
||||||
* {@link SystemCommand}s when a SystemCommand has been called.
|
|
||||||
*
|
|
||||||
* @param consumer the action to perform when this {@link SystemCommand} has
|
|
||||||
* been called
|
|
||||||
* @since Envoy Client v0.2-beta
|
|
||||||
*/
|
|
||||||
void onCall(Supplier<Void> consumer);
|
|
||||||
}
|
|
@ -1,23 +1,20 @@
|
|||||||
package envoy.client.data.commands;
|
package envoy.client.data.commands;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.function.*;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class is the base class of all {@code SystemCommands} and contains an
|
* This class is the base class of all {@code SystemCommands} and contains an action and a number of
|
||||||
* action and a number of arguments that should be used as input for this
|
* arguments that should be used as input for this function. No {@code SystemCommand} can return
|
||||||
* function.
|
* anything. Every {@code SystemCommand} must have as argument type {@code List<String>} so that the
|
||||||
* No {@code SystemCommand} can return anything.
|
* words following the indicator String can be used as input of the function. This approach has one
|
||||||
* Every {@code SystemCommand} must have as argument type {@code List<String>} so
|
* limitation:<br>
|
||||||
* that the words following the indicator String can be used as input of the
|
* <b>Order matters!</b> Changing the order of arguments will likely result in unexpected behavior.
|
||||||
* function. This approach has one limitation:<br>
|
|
||||||
* <b>Order matters!</b> Changing the order of arguments will likely result in
|
|
||||||
* unexpected behavior.
|
|
||||||
*
|
*
|
||||||
* @author Leon Hofmeister
|
* @author Leon Hofmeister
|
||||||
* @since Envoy Client v0.2-beta
|
* @since Envoy Client v0.2-beta
|
||||||
*/
|
*/
|
||||||
public final class SystemCommand implements OnCall {
|
public final class SystemCommand implements Callable {
|
||||||
|
|
||||||
protected int relevance;
|
protected int relevance;
|
||||||
|
|
||||||
@ -28,8 +25,8 @@ public final class SystemCommand implements OnCall {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* This function takes a {@code List<String>} as argument because automatically
|
* This function takes a {@code List<String>} as argument because automatically
|
||||||
* {@code SystemCommand#numberOfArguments} words following the necessary command
|
* {@code SystemCommand#numberOfArguments} words following the necessary command will be put
|
||||||
* will be put into this list.
|
* into this list.
|
||||||
*
|
*
|
||||||
* @see String#split(String)
|
* @see String#split(String)
|
||||||
*/
|
*/
|
||||||
@ -48,19 +45,14 @@ public final class SystemCommand implements OnCall {
|
|||||||
* @param description the description of this {@code SystemCommand}
|
* @param description the description of this {@code SystemCommand}
|
||||||
* @since Envoy Client v0.2-beta
|
* @since Envoy Client v0.2-beta
|
||||||
*/
|
*/
|
||||||
public SystemCommand(Consumer<List<String>> action, int numberOfArguments, List<String> defaults, String description) {
|
public SystemCommand(Consumer<List<String>> action, int numberOfArguments,
|
||||||
|
List<String> defaults, String description) {
|
||||||
this.numberOfArguments = numberOfArguments;
|
this.numberOfArguments = numberOfArguments;
|
||||||
this.action = action;
|
this.action = action;
|
||||||
this.defaults = defaults == null ? new ArrayList<>() : defaults;
|
this.defaults = defaults == null ? new ArrayList<>() : defaults;
|
||||||
this.description = description;
|
this.description = description;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the action that should be performed
|
|
||||||
* @since Envoy Client v0.2-beta
|
|
||||||
*/
|
|
||||||
public Consumer<List<String>> getAction() { return action; }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the argument count of the command
|
* @return the argument count of the command
|
||||||
* @since Envoy Client v0.2-beta
|
* @since Envoy Client v0.2-beta
|
||||||
@ -85,20 +77,10 @@ public final class SystemCommand implements OnCall {
|
|||||||
*/
|
*/
|
||||||
public void setRelevance(int relevance) { this.relevance = relevance; }
|
public void setRelevance(int relevance) { this.relevance = relevance; }
|
||||||
|
|
||||||
/**
|
|
||||||
* Increments the relevance of this {@code SystemCommand}.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void onCall() { relevance++; }
|
public void call(List<String> arguments) {
|
||||||
|
action.accept(arguments);
|
||||||
/**
|
++relevance;
|
||||||
* Increments the relevance of this {@code SystemCommand} and executes the
|
|
||||||
* supplier.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void onCall(Supplier<Void> consumer) {
|
|
||||||
onCall();
|
|
||||||
consumer.get();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -108,21 +90,27 @@ public final class SystemCommand implements OnCall {
|
|||||||
public List<String> getDefaults() { return defaults; }
|
public List<String> getDefaults() { return defaults; }
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() { return Objects.hash(action); }
|
public int hashCode() {
|
||||||
|
return Objects.hash(action);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object obj) {
|
public boolean equals(Object obj) {
|
||||||
if (this == obj) return true;
|
if (this == obj)
|
||||||
if (obj == null) return false;
|
return true;
|
||||||
if (getClass() != obj.getClass()) return false;
|
if (obj == null)
|
||||||
final SystemCommand other = (SystemCommand) obj;
|
return false;
|
||||||
|
if (getClass() != obj.getClass())
|
||||||
|
return false;
|
||||||
|
final var other = (SystemCommand) obj;
|
||||||
return Objects.equals(action, other.action);
|
return Objects.equals(action, other.action);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "SystemCommand [relevance=" + relevance + ", numberOfArguments=" + numberOfArguments + ", "
|
return "SystemCommand [relevance=" + relevance + ", numberOfArguments=" + numberOfArguments
|
||||||
+ (action != null ? "action=" + action + ", " : "") + (description != null ? "description=" + description + ", " : "")
|
+ ", "
|
||||||
|
+ (description != null ? "description=" + description + ", " : "")
|
||||||
+ (defaults != null ? "defaults=" + defaults : "") + "]";
|
+ (defaults != null ? "defaults=" + defaults : "") + "]";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,18 +20,21 @@ public final class SystemCommandBuilder {
|
|||||||
private final SystemCommandMap commandsMap;
|
private final SystemCommandMap commandsMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new {@code SystemCommandsBuilder} without underlying
|
* Creates a new {@code SystemCommandsBuilder} without underlying {@link SystemCommandMap}.
|
||||||
* {@link SystemCommandMap}.
|
|
||||||
*
|
*
|
||||||
* @since Envoy Client v0.2-beta
|
* @since Envoy Client v0.2-beta
|
||||||
*/
|
*/
|
||||||
public SystemCommandBuilder() { this(null); }
|
public SystemCommandBuilder() {
|
||||||
|
this(null);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param commandsMap the map to use when calling build (optional)
|
* @param commandsMap the map to use when calling build (optional)
|
||||||
* @since Envoy Client v0.2-beta
|
* @since Envoy Client v0.2-beta
|
||||||
*/
|
*/
|
||||||
public SystemCommandBuilder(SystemCommandMap commandsMap) { this.commandsMap = commandsMap; }
|
public SystemCommandBuilder(SystemCommandMap commandsMap) {
|
||||||
|
this.commandsMap = commandsMap;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param numberOfArguments the numberOfArguments to set
|
* @param numberOfArguments the numberOfArguments to set
|
||||||
@ -104,12 +107,14 @@ public final class SystemCommandBuilder {
|
|||||||
* @return the built {@code SystemCommand}
|
* @return the built {@code SystemCommand}
|
||||||
* @since Envoy Client v0.2-beta
|
* @since Envoy Client v0.2-beta
|
||||||
*/
|
*/
|
||||||
public SystemCommand build() { return build(true); }
|
public SystemCommand build() {
|
||||||
|
return build(true);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds a {@code SystemCommand} based upon the previously entered data.<br>
|
* Builds a {@code SystemCommand} based upon the previously entered data.<br>
|
||||||
* {@code SystemCommand#numberOfArguments} will be set to 0, regardless of the
|
* {@code SystemCommand#numberOfArguments} will be set to 0, regardless of the previous
|
||||||
* previous value.<br>
|
* value.<br>
|
||||||
* At the end, this {@code SystemCommandBuilder} will be reset.
|
* At the end, this {@code SystemCommandBuilder} will be reset.
|
||||||
*
|
*
|
||||||
* @return the built {@code SystemCommand}
|
* @return the built {@code SystemCommand}
|
||||||
@ -122,8 +127,8 @@ public final class SystemCommandBuilder {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds a {@code SystemCommand} based upon the previously entered data.<br>
|
* Builds a {@code SystemCommand} based upon the previously entered data.<br>
|
||||||
* {@code SystemCommand#numberOfArguments} will be set to use the rest of the
|
* {@code SystemCommand#numberOfArguments} will be set to use the rest of the string as
|
||||||
* string as argument, regardless of the previous value.<br>
|
* argument, regardless of the previous value.<br>
|
||||||
* At the end, this {@code SystemCommandBuilder} will be reset.
|
* At the end, this {@code SystemCommandBuilder} will be reset.
|
||||||
*
|
*
|
||||||
* @return the built {@code SystemCommand}
|
* @return the built {@code SystemCommand}
|
||||||
@ -136,27 +141,25 @@ public final class SystemCommandBuilder {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds a {@code SystemCommand} based upon the previously entered data.<br>
|
* Builds a {@code SystemCommand} based upon the previously entered data.<br>
|
||||||
* Automatically adds the built object to the given map.
|
* Automatically adds the built object to the given map. At the end, this
|
||||||
* At the end, this {@code SystemCommandBuilder} <b>can</b> be reset but must
|
* {@code SystemCommandBuilder} <b>can</b> be reset but must not be.
|
||||||
* not be.
|
|
||||||
*
|
*
|
||||||
* @param reset whether this {@code SystemCommandBuilder} should be reset
|
* @param reset whether this {@code SystemCommandBuilder} should be reset afterwards.<br>
|
||||||
* afterwards.<br>
|
* This can be useful if another command wants to execute something similar
|
||||||
* This can be useful if another command wants to execute something
|
|
||||||
* similar
|
|
||||||
* @return the built {@code SystemCommand}
|
* @return the built {@code SystemCommand}
|
||||||
* @since Envoy Client v0.2-beta
|
* @since Envoy Client v0.2-beta
|
||||||
*/
|
*/
|
||||||
public SystemCommand build(boolean reset) {
|
public SystemCommand build(boolean reset) {
|
||||||
final var sc = new SystemCommand(action, numberOfArguments, defaults, description);
|
final var sc = new SystemCommand(action, numberOfArguments, defaults, description);
|
||||||
sc.setRelevance(relevance);
|
sc.setRelevance(relevance);
|
||||||
if (reset) reset();
|
if (reset)
|
||||||
|
reset();
|
||||||
return sc;
|
return sc;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds a {@code SystemCommand} based upon the previously entered data.
|
* Builds a {@code SystemCommand} based upon the previously entered data. Automatically adds the
|
||||||
* Automatically adds the built object to the given map.
|
* built object to the given map.
|
||||||
*
|
*
|
||||||
* @param command the command under which to store the SystemCommand in the
|
* @param command the command under which to store the SystemCommand in the
|
||||||
* {@link SystemCommandMap}
|
* {@link SystemCommandMap}
|
||||||
@ -164,13 +167,14 @@ public final class SystemCommandBuilder {
|
|||||||
* @throws NullPointerException if no map has been assigned to this builder
|
* @throws NullPointerException if no map has been assigned to this builder
|
||||||
* @since Envoy Client v0.2-beta
|
* @since Envoy Client v0.2-beta
|
||||||
*/
|
*/
|
||||||
public SystemCommand build(String command) { return build(command, true); }
|
public SystemCommand build(String command) {
|
||||||
|
return build(command, true);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds a {@code SystemCommand} based upon the previously entered data.<br>
|
* Builds a {@code SystemCommand} based upon the previously entered data.<br>
|
||||||
* Automatically adds the built object to the given map.
|
* Automatically adds the built object to the given map. {@code SystemCommand#numberOfArguments}
|
||||||
* {@code SystemCommand#numberOfArguments} will be set to 0, regardless of the
|
* will be set to 0, regardless of the previous value.<br>
|
||||||
* previous value.<br>
|
|
||||||
* At the end, this {@code SystemCommandBuilder} will be reset.
|
* At the end, this {@code SystemCommandBuilder} will be reset.
|
||||||
*
|
*
|
||||||
* @param command the command under which to store the SystemCommand in the
|
* @param command the command under which to store the SystemCommand in the
|
||||||
@ -186,9 +190,8 @@ public final class SystemCommandBuilder {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds a {@code SystemCommand} based upon the previously entered data.<br>
|
* Builds a {@code SystemCommand} based upon the previously entered data.<br>
|
||||||
* Automatically adds the built object to the given map.
|
* Automatically adds the built object to the given map. {@code SystemCommand#numberOfArguments}
|
||||||
* {@code SystemCommand#numberOfArguments} will be set to use the rest of the
|
* will be set to use the rest of the string as argument, regardless of the previous value.<br>
|
||||||
* string as argument, regardless of the previous value.<br>
|
|
||||||
* At the end, this {@code SystemCommandBuilder} will be reset.
|
* At the end, this {@code SystemCommandBuilder} will be reset.
|
||||||
*
|
*
|
||||||
* @param command the command under which to store the SystemCommand in the
|
* @param command the command under which to store the SystemCommand in the
|
||||||
@ -204,17 +207,13 @@ public final class SystemCommandBuilder {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds a {@code SystemCommand} based upon the previously entered data.<br>
|
* Builds a {@code SystemCommand} based upon the previously entered data.<br>
|
||||||
* Automatically adds the built object to the given map.
|
* Automatically adds the built object to the given map. At the end, this
|
||||||
* At the end, this {@code SystemCommandBuilder} <b>can</b> be reset but must
|
* {@code SystemCommandBuilder} <b>can</b> be reset but must not be.
|
||||||
* not be.
|
|
||||||
*
|
*
|
||||||
* @param command the command under which to store the SystemCommand in the
|
* @param command the command under which to store the SystemCommand in the
|
||||||
* {@link SystemCommandMap}
|
* {@link SystemCommandMap}
|
||||||
* @param reset whether this {@code SystemCommandBuilder} should be reset
|
* @param reset whether this {@code SystemCommandBuilder} should be reset afterwards.<br>
|
||||||
* afterwards.<br>
|
* This can be useful if another command wants to execute something similar
|
||||||
* This can be useful if another command wants to execute
|
|
||||||
* something
|
|
||||||
* similar
|
|
||||||
* @return the built {@code SystemCommand}
|
* @return the built {@code SystemCommand}
|
||||||
* @throws NullPointerException if no map has been assigned to this builder
|
* @throws NullPointerException if no map has been assigned to this builder
|
||||||
* @since Envoy Client v0.2-beta
|
* @since Envoy Client v0.2-beta
|
||||||
@ -222,9 +221,12 @@ public final class SystemCommandBuilder {
|
|||||||
public SystemCommand build(String command, boolean reset) {
|
public SystemCommand build(String command, boolean reset) {
|
||||||
final var sc = new SystemCommand(action, numberOfArguments, defaults, description);
|
final var sc = new SystemCommand(action, numberOfArguments, defaults, description);
|
||||||
sc.setRelevance(relevance);
|
sc.setRelevance(relevance);
|
||||||
if (commandsMap != null) commandsMap.add(command, sc);
|
if (commandsMap != null)
|
||||||
else throw new NullPointerException("No map in SystemCommandsBuilder present");
|
commandsMap.add(command, sc);
|
||||||
if (reset) reset();
|
else
|
||||||
|
throw new NullPointerException("No map in SystemCommandsBuilder present");
|
||||||
|
if (reset)
|
||||||
|
reset();
|
||||||
return sc;
|
return sc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,162 +1,243 @@
|
|||||||
package envoy.client.data.commands;
|
package envoy.client.data.commands;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.logging.*;
|
import java.util.logging.*;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.scene.control.Alert;
|
||||||
|
import javafx.scene.control.Alert.AlertType;
|
||||||
|
|
||||||
import envoy.util.EnvoyLog;
|
import envoy.util.EnvoyLog;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class stores all {@link SystemCommand}s used.
|
* Stores all {@link SystemCommand}s used. SystemCommands can be called using an activator char and
|
||||||
|
* the text that needs to be present behind the activator. Additionally offers the option to request
|
||||||
|
* recommendations for a partial input String.
|
||||||
*
|
*
|
||||||
* @author Leon Hofmeister
|
* @author Leon Hofmeister
|
||||||
* @since Envoy Client v0.2-beta
|
* @since Envoy Client v0.2-beta
|
||||||
*/
|
*/
|
||||||
public final class SystemCommandMap {
|
public final class SystemCommandMap {
|
||||||
|
|
||||||
|
private final Character activator;
|
||||||
private final Map<String, SystemCommand> systemCommands = new HashMap<>();
|
private final Map<String, SystemCommand> systemCommands = new HashMap<>();
|
||||||
|
private final Pattern commandPattern =
|
||||||
private final Pattern commandPattern = Pattern.compile("^[a-zA-Z0-9_:!\\(\\)\\?\\.\\,\\;\\-]+$");
|
Pattern.compile("^[a-zA-Z0-9_:!/\\(\\)\\?\\.\\,\\;\\-]+$");
|
||||||
|
|
||||||
private static final Logger logger = EnvoyLog.getLogger(SystemCommandMap.class);
|
private static final Logger logger = EnvoyLog.getLogger(SystemCommandMap.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@code SystemCommandMap} with the given char as activator. If this Character is
|
||||||
|
* null, any text used as input will be treated as a system command.
|
||||||
|
*
|
||||||
|
* @param activator the char to use as activator for commands
|
||||||
|
* @since Envoy Client v0.3-beta
|
||||||
|
*/
|
||||||
|
public SystemCommandMap(Character activator) {
|
||||||
|
this.activator = activator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@code SystemCommandMap} with '/' as activator.
|
||||||
|
*
|
||||||
|
* @since Envoy Client v0.3-beta
|
||||||
|
*/
|
||||||
|
public SystemCommandMap() {
|
||||||
|
activator = '/';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a new command to the map if the command name is valid.
|
* Adds a new command to the map if the command name is valid.
|
||||||
*
|
*
|
||||||
* @param command the input string to execute the
|
* @param command the input string to execute the given action
|
||||||
* given action
|
* @param systemCommand the command to add - can be built using {@link SystemCommandBuilder}
|
||||||
* @param systemCommand the command to add - can be built using
|
|
||||||
* {@link SystemCommandBuilder}
|
|
||||||
* @see SystemCommandMap#isValidKey(String)
|
* @see SystemCommandMap#isValidKey(String)
|
||||||
* @since Envoy Client v0.2-beta
|
* @since Envoy Client v0.2-beta
|
||||||
*/
|
*/
|
||||||
public void add(String command, SystemCommand systemCommand) {
|
public void add(String command, SystemCommand systemCommand) {
|
||||||
if (isValidKey(command)) systemCommands.put(command.toLowerCase(), systemCommand);
|
if (isValidKey(command))
|
||||||
|
systemCommands.put(command.toLowerCase(), systemCommand);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method checks if the input String is a key in the map and returns the
|
* This method checks if the input String is a key in the map and returns the wrapped System
|
||||||
* wrapped System command if present.
|
* command if present.
|
||||||
* It will return an empty optional if the value after the slash is not a key in
|
|
||||||
* the map, which is a valid case (i.e. input="3/4" and "4" is not a key in the
|
|
||||||
* map).
|
|
||||||
* <p>
|
* <p>
|
||||||
* Usage example:<br>
|
* Usage example:<br>
|
||||||
* {@code SystemCommandMap systemCommands = new SystemCommandMap();}<br>
|
* {@code SystemCommandMap systemCommands = new SystemCommandMap('*');}<br>
|
||||||
* {@code Button button = new Button();}
|
* {@code systemCommands.add("example", new SystemCommand(text -> {}, 1, null, ""));}<br>
|
||||||
* {@code systemCommands.add("example", text -> button.setText(text.get(0), 1);}<br>
|
|
||||||
* {@code ....}<br>
|
* {@code ....}<br>
|
||||||
* user input: {@code "/example xyz ..."}<br>
|
* user input: {@code "*example xyz ..."}<br>
|
||||||
* {@code systemCommands.get("example xyz ...")} or
|
* {@code systemCommands.get("example xyz ...")} or
|
||||||
* {@code systemCommands.get("/example xyz ...")}
|
* {@code systemCommands.get("*example xyz ...")} result:
|
||||||
* result: {@code Optional<SystemCommand>}
|
* {@code Optional<SystemCommand>.get() != null}
|
||||||
*
|
*
|
||||||
* @param input the input string given by the user
|
* @param input the input string given by the user
|
||||||
* @return the wrapped system command, if present
|
* @return the wrapped system command, if present
|
||||||
* @since Envoy Client v0.2-beta
|
* @since Envoy Client v0.2-beta
|
||||||
*/
|
*/
|
||||||
public Optional<SystemCommand> get(String input) { return Optional.ofNullable(systemCommands.get(getCommand(input.toLowerCase()))); }
|
public Optional<SystemCommand> get(String input) {
|
||||||
|
return Optional.ofNullable(systemCommands.get(getCommand(input.toLowerCase())));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method ensures that the "/" of a {@link SystemCommand} is stripped.<br>
|
* This method ensures that the activator of a {@link SystemCommand} is stripped.<br>
|
||||||
* It returns the command as (most likely) entered as key in the map for the
|
* It only checks the word beginning from the first non-blank position in the input. It returns
|
||||||
* first word of the text.<br>
|
* the command as (most likely) entered as key in the map for the first word of the text.<br>
|
||||||
* It should only be called on strings that contain a "/" at position 0/-1.
|
* Activators in the middle of the word will be disregarded.
|
||||||
*
|
*
|
||||||
* @param raw the input
|
* @param raw the input
|
||||||
* @return the command as entered in the map
|
* @return the command as entered in the map
|
||||||
* @since Envoy Client v0.2-beta
|
* @since Envoy Client v0.2-beta
|
||||||
* @apiNote this method will (most likely) not return anything useful if
|
* @apiNote this method will (most likely) not return anything useful if whatever is entered
|
||||||
* whatever is entered after the slash is not a system command. Only
|
* after the activator is not a system command. Only exception: for recommendation
|
||||||
* exception: for recommendation purposes.
|
* purposes.
|
||||||
*/
|
*/
|
||||||
public String getCommand(String raw) {
|
public String getCommand(String raw) {
|
||||||
final var trimmed = raw.stripLeading();
|
final var trimmed = raw.stripLeading();
|
||||||
|
|
||||||
|
// Entering only the activator should not throw an error
|
||||||
|
if (trimmed.length() == 1 && activator != null && activator.equals(trimmed.charAt(0)))
|
||||||
|
return "";
|
||||||
|
else {
|
||||||
final var index = trimmed.indexOf(' ');
|
final var index = trimmed.indexOf(' ');
|
||||||
return trimmed.substring(trimmed.charAt(0) == '/' ? 1 : 0, index < 1 ? trimmed.length() : index);
|
return trimmed.substring(
|
||||||
|
activator != null && activator.equals(trimmed.charAt(0)) ? 1 : 0,
|
||||||
|
index < 1 ? trimmed.length() : index);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Examines whether a key can be put in the map and logs it with
|
* Examines whether a key can be put in the map and logs it with {@code Level.WARNING} if that
|
||||||
* {@code Level.WARNING} if that key violates API constrictions.<br>
|
* key violates API constrictions.<br>
|
||||||
* (allowed chars are <b>a-zA-Z0-9_:!()?.,;-</b>)
|
* (allowed chars are <b>a-zA-Z0-9_:!/()?.,;-</b>)
|
||||||
* <p>
|
* <p>
|
||||||
* The approach to not throw an exception was taken so that an ugly try-catch
|
* The approach to not throw an exception was taken so that an ugly try-catch block for every
|
||||||
* block for every addition to the system commands map could be avoided, an
|
* addition to the system commands map could be avoided, an error that should only occur during
|
||||||
* error that should only occur during implementation and not in production.
|
* implementation and not in production.
|
||||||
*
|
*
|
||||||
* @param command the key to examine
|
* @param command the key to examine
|
||||||
* @return whether this key can be used in the map
|
* @return whether this key can be used in the map
|
||||||
* @since Envoy Client v0.2-beta
|
* @since Envoy Client v0.2-beta
|
||||||
*/
|
*/
|
||||||
public boolean isValidKey(String command) {
|
public boolean isValidKey(String command) {
|
||||||
final boolean valid = commandPattern.matcher(command).matches();
|
final var valid = commandPattern.matcher(command).matches();
|
||||||
if (!valid) logger.log(Level.WARNING,
|
if (!valid)
|
||||||
|
logger.log(Level.WARNING,
|
||||||
"The command \"" + command
|
"The command \"" + command
|
||||||
+ "\" is not valid. As it will cause problems in execution, it will not be entered into the map. Only the characters "
|
+ "\" is not valid. As it might cause problems when executed, it will not be entered into the map. Only the characters "
|
||||||
+ commandPattern + "are allowed");
|
+ commandPattern + "are allowed");
|
||||||
return valid;
|
return valid;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Takes a 'raw' string (the whole input) and checks if "/" is the first visible
|
* Takes a 'raw' string (the whole input) and checks if the activator is the first visible
|
||||||
* character and then checks if a command is present after that "/". If that is
|
* character and then checks if a command is present after that activator. If that is the case,
|
||||||
* the case, it will be executed.
|
* it will be executed.
|
||||||
* <p>
|
|
||||||
*
|
*
|
||||||
* @param raw the raw input string
|
* @param raw the raw input string
|
||||||
* @return whether a command could be found
|
* @return whether a command could be found and successfully executed
|
||||||
* @since Envoy Client v0.2-beta
|
* @since Envoy Client v0.2-beta
|
||||||
*/
|
*/
|
||||||
public boolean executeIfAnyPresent(String raw) {
|
public boolean executeIfPresent(String raw) {
|
||||||
|
|
||||||
// possibly a command was detected and could be executed
|
// possibly a command was detected and could be executed
|
||||||
final var raw2 = raw.stripLeading();
|
final var raw2 = raw.stripLeading();
|
||||||
final var commandFound = raw2.startsWith("/") ? executeIfPresent(raw2) : false;
|
final var commandFound = activator == null || raw2.startsWith(activator.toString())
|
||||||
|
? executeAvailableCommand(raw2)
|
||||||
|
: false;
|
||||||
|
|
||||||
// the command was executed successfully - no further checking needed
|
// the command was executed successfully - no further checking needed
|
||||||
if (commandFound) logger.log(Level.FINE, "executed system command " + getCommand(raw2));
|
if (commandFound)
|
||||||
|
logger.log(Level.FINE, "executed system command " + getCommand(raw2));
|
||||||
return commandFound;
|
return commandFound;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method checks if the input String is a key in the map and executes the
|
* Retrieves the recommendations based on the current input entered.<br>
|
||||||
* wrapped System command if present.
|
* The first word is used for the recommendations and it does not matter if the activator is at
|
||||||
* Its intended usage is after a "/" has been detected in the input String.
|
* its beginning or not.<br>
|
||||||
* It will do nothing if the value after the slash is not a key in
|
* If recommendations are present, the given function will be executed on the
|
||||||
* the map, which is a valid case (i.e. input="3/4" and "4" is not a key in the
|
* recommendations.<br>
|
||||||
* map).
|
* Otherwise nothing will be done.<br>
|
||||||
* <p>
|
|
||||||
* Usage example:<br>
|
|
||||||
* {@code SystemCommandMap systemCommands = new SystemCommandMap();}<br>
|
|
||||||
* {@code Button button = new Button();}<br>
|
|
||||||
* {@code systemCommands.add("example", (words)-> button.setText(words.get(0), 1);}<br>
|
|
||||||
* {@code ....}<br>
|
|
||||||
* user input: {@code "/example xyz ..."}<br>
|
|
||||||
* {@code systemCommands.executeIfPresent("example xyz ...")}
|
|
||||||
* result: {@code button.getText()=="xyz"}
|
|
||||||
*
|
*
|
||||||
* @param input the input string given by the user
|
* @param input the input string
|
||||||
* @return whether a command could be found
|
* @param action the action that should be taken for the recommendations, if any are present
|
||||||
* @since Envoy Client v0.2-beta
|
* @since Envoy Client v0.2-beta
|
||||||
*/
|
*/
|
||||||
public boolean executeIfPresent(String input) {
|
public void requestRecommendations(String input, Consumer<Set<String>> action) {
|
||||||
|
final var partialCommand = getCommand(input);
|
||||||
|
|
||||||
|
// Get the expected commands
|
||||||
|
final var recommendations = recommendCommands(partialCommand);
|
||||||
|
if (recommendations.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Execute the given action
|
||||||
|
else
|
||||||
|
action.accept(recommendations);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method checks if the input String is a key in the map and executes the wrapped System
|
||||||
|
* command if present.
|
||||||
|
* <p>
|
||||||
|
* Usage example:<br>
|
||||||
|
* {@code SystemCommandMap systemCommands = new SystemCommandMap('*');}<br>
|
||||||
|
* {@code Button button = new Button();}<br>
|
||||||
|
* {@code systemCommands.add("example", new SystemCommand(text -> {button.setText(text.get(0))},
|
||||||
|
* 1, null, ""));}<br>
|
||||||
|
* {@code ....}<br>
|
||||||
|
* user input: {@code "*example xyz ..."}<br>
|
||||||
|
* {@code systemCommands.executeIfPresent("example xyz ...")} or
|
||||||
|
* {@code systemCommands.executeIfPresent("*example xyz ...")} result:
|
||||||
|
* {@code button.getText()=="xyz"}
|
||||||
|
*
|
||||||
|
* @param input the input string given by the user
|
||||||
|
* @return whether a command could be found and successfully executed
|
||||||
|
* @since Envoy Client v0.2-beta
|
||||||
|
*/
|
||||||
|
private boolean executeAvailableCommand(String input) {
|
||||||
final var command = getCommand(input);
|
final var command = getCommand(input);
|
||||||
final var value = get(command);
|
final var value = get(command);
|
||||||
|
final var commandExecuted = new AtomicBoolean(value.isPresent());
|
||||||
value.ifPresent(systemCommand -> {
|
value.ifPresent(systemCommand -> {
|
||||||
|
|
||||||
// Splitting the String so that the leading command including the first " " is
|
// Splitting the String so that the leading command including the first " " is
|
||||||
// removed and only as many following words as allowed by the system command
|
// removed and only as many following words as allowed by the system command
|
||||||
// persist
|
// persist
|
||||||
final var arguments = extractArguments(input, systemCommand);
|
final var arguments = extractArguments(input, systemCommand);
|
||||||
|
|
||||||
// Executing the function
|
// Executing the function
|
||||||
try {
|
try {
|
||||||
systemCommand.getAction().accept(arguments);
|
systemCommand.call(arguments);
|
||||||
systemCommand.onCall();
|
} catch (final NumberFormatException e) {
|
||||||
|
logger.log(Level.INFO,
|
||||||
|
String.format(
|
||||||
|
"System command %s could not be performed correctly because the user is a dumbass and could not write a parseable number.",
|
||||||
|
command));
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
final var alert = new Alert(AlertType.ERROR);
|
||||||
|
alert.setContentText("Please enter a readable number as argument.");
|
||||||
|
alert.showAndWait();
|
||||||
|
});
|
||||||
|
commandExecuted.set(false);
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
logger.log(Level.WARNING, "The system command " + command + " threw an exception: ", e);
|
logger.log(Level.WARNING, "System command " + command + " threw an exception: ", e);
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
final var alert = new Alert(AlertType.ERROR);
|
||||||
|
alert.setContentText(
|
||||||
|
"Could not execute system command: Internal error. Please insult the responsible programmer.");
|
||||||
|
alert.showAndWait();
|
||||||
|
});
|
||||||
|
commandExecuted.set(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return value.isPresent();
|
return commandExecuted.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -168,86 +249,75 @@ public final class SystemCommandMap {
|
|||||||
* @since Envoy Client v0.2-beta
|
* @since Envoy Client v0.2-beta
|
||||||
*/
|
*/
|
||||||
private List<String> extractArguments(String input, SystemCommand systemCommand) {
|
private List<String> extractArguments(String input, SystemCommand systemCommand) {
|
||||||
|
|
||||||
// no more arguments follow after the command (e.g. text = "/DABR")
|
// no more arguments follow after the command (e.g. text = "/DABR")
|
||||||
final var indexOfSpace = input.indexOf(" ");
|
final var indexOfSpace = input.indexOf(" ");
|
||||||
if (indexOfSpace < 0) return supplementDefaults(new String[] {}, systemCommand);
|
if (indexOfSpace < 0)
|
||||||
|
return supplementDefaults(new String[] {}, systemCommand);
|
||||||
|
|
||||||
// the arguments behind a system command
|
// the arguments behind a system command
|
||||||
final var remainingString = input.substring(indexOfSpace + 1);
|
final var remainingString = input.substring(indexOfSpace + 1);
|
||||||
final var numberOfArguments = systemCommand.getNumberOfArguments();
|
final var numberOfArguments = systemCommand.getNumberOfArguments();
|
||||||
|
|
||||||
// splitting those arguments and supplying default values
|
// splitting those arguments and supplying default values
|
||||||
final var textArguments = remainingString.split(" ", -1);
|
final var textArguments = remainingString.split(" ", -1);
|
||||||
final var originalArguments = numberOfArguments >= 0 ? Arrays.copyOfRange(textArguments, 0, numberOfArguments) : textArguments;
|
final var originalArguments =
|
||||||
|
numberOfArguments >= 0 ? Arrays.copyOfRange(textArguments, 0, numberOfArguments)
|
||||||
|
: textArguments;
|
||||||
final var arguments = supplementDefaults(originalArguments, systemCommand);
|
final var arguments = supplementDefaults(originalArguments, systemCommand);
|
||||||
return arguments;
|
return arguments;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the recommendations based on the current input entered.<br>
|
|
||||||
* The first word is used for the recommendations and
|
|
||||||
* it does not matter if the "/" is at its beginning or not.<br>
|
|
||||||
* If none are present, nothing will be done.<br>
|
|
||||||
* Otherwise the given function will be executed on the recommendations.<br>
|
|
||||||
*
|
|
||||||
* @param input the input string
|
|
||||||
* @param action the action that should be taken for the recommendations, if any
|
|
||||||
* are present
|
|
||||||
* @since Envoy Client v0.2-beta
|
|
||||||
*/
|
|
||||||
public void requestRecommendations(String input, Consumer<Set<String>> action) {
|
|
||||||
final var partialCommand = getCommand(input);
|
|
||||||
// Get the expected commands
|
|
||||||
final var recommendations = recommendCommands(partialCommand);
|
|
||||||
if (recommendations.isEmpty()) return;
|
|
||||||
// Execute the given action
|
|
||||||
else action.accept(recommendations);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recommends commands based upon the currently entered input.<br>
|
* Recommends commands based upon the currently entered input.<br>
|
||||||
* In the current implementation, all we check is whether a key contains this
|
* In the current implementation, all that gets checked is whether a key contains this input.
|
||||||
* input. This might be updated later on.
|
* This might be updated later on.
|
||||||
*
|
*
|
||||||
* @param partialCommand the partially entered command
|
* @param partialCommand the partially entered command
|
||||||
* @return a set of all commands that match this input
|
* @return a set of all commands that match this input
|
||||||
* @since Envoy Client v0.2-beta
|
* @since Envoy Client v0.2-beta
|
||||||
*/
|
*/
|
||||||
private Set<String> recommendCommands(String partialCommand) {
|
private Set<String> recommendCommands(String partialCommand) {
|
||||||
|
|
||||||
// current implementation only looks if input is contained within a command,
|
// current implementation only looks if input is contained within a command,
|
||||||
// might be updated
|
// might be updated
|
||||||
return systemCommands.keySet()
|
return systemCommands.keySet()
|
||||||
.stream()
|
.stream()
|
||||||
.filter(command -> command.contains(partialCommand))
|
.filter(command -> command.contains(partialCommand))
|
||||||
.sorted((command1, command2) -> Integer.compare(systemCommands.get(command1).getRelevance(), systemCommands.get(command2).getRelevance()))
|
.sorted(
|
||||||
|
(command1, command2) -> Integer.compare(systemCommands.get(command1).getRelevance(),
|
||||||
|
systemCommands.get(command2).getRelevance()))
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Supplies the default values for arguments if none are present in the text for any argument.
|
||||||
* Supplies the default values for arguments if none are present in the text for
|
* <br>
|
||||||
* any argument. <br>
|
|
||||||
* Will only work for {@code SystemCommand}s whose argument counter is bigger
|
|
||||||
* than 1.
|
|
||||||
*
|
*
|
||||||
* @param textArguments the arguments that were parsed from the text
|
* @param textArguments the arguments that were parsed from the text
|
||||||
* @param toEvaluate the system command whose default values should be used
|
* @param toEvaluate the system command whose default values should be used
|
||||||
* @return the final argument list
|
* @return the final argument list
|
||||||
* @since Envoy Client v0.2-beta
|
* @since Envoy Client v0.2-beta
|
||||||
* @apiNote this method will insert an empty String if the size of the list
|
* @apiNote this method will insert an empty String if the size of the list given to the
|
||||||
* given to the {@code SystemCommand} is smaller than its argument
|
* {@code SystemCommand} is smaller than its argument counter and no more text
|
||||||
* counter and no more text arguments could be found.
|
* arguments could be found.
|
||||||
*/
|
*/
|
||||||
private List<String> supplementDefaults(String[] textArguments, SystemCommand toEvaluate) {
|
private List<String> supplementDefaults(String[] textArguments, SystemCommand toEvaluate) {
|
||||||
final var defaults = toEvaluate.getDefaults();
|
final var defaults = toEvaluate.getDefaults();
|
||||||
final var numberOfArguments = toEvaluate.getNumberOfArguments();
|
final var numberOfArguments = toEvaluate.getNumberOfArguments();
|
||||||
final List<String> result = new ArrayList<>();
|
final List<String> result = new ArrayList<>();
|
||||||
|
|
||||||
if (toEvaluate.getNumberOfArguments() > 0) for (int index = 0; index < numberOfArguments; index++) {
|
if (toEvaluate.getNumberOfArguments() > 0)
|
||||||
|
for (var index = 0; index < numberOfArguments; index++) {
|
||||||
String textArg = null;
|
String textArg = null;
|
||||||
if (index < textArguments.length) textArg = textArguments[index];
|
if (index < textArguments.length)
|
||||||
|
textArg = textArguments[index];
|
||||||
|
|
||||||
// Set the argument at position index to the current argument of the text, if it
|
// Set the argument at position index to the current argument of the text, if it
|
||||||
// is present. Otherwise the default for that argument will be taken if present.
|
// is present. Otherwise the default for that argument will be taken if present.
|
||||||
// In the worst case, an empty String will be used.
|
// In the worst case, an empty String will be used.
|
||||||
result.add(!(textArg == null) && !textArg.isBlank() ? textArg : index < defaults.size() ? defaults.get(index) : "");
|
result.add(!(textArg == null) && !textArg.isBlank() ? textArg
|
||||||
|
: index < defaults.size() ? defaults.get(index) : "");
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@ -257,4 +327,10 @@ public final class SystemCommandMap {
|
|||||||
* @since Envoy Client v0.2-beta
|
* @since Envoy Client v0.2-beta
|
||||||
*/
|
*/
|
||||||
public Map<String, SystemCommand> getSystemCommands() { return systemCommands; }
|
public Map<String, SystemCommand> getSystemCommands() { return systemCommands; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the activator of any command in this map. Can be null.
|
||||||
|
* @since Envoy Client v0.3-beta
|
||||||
|
*/
|
||||||
|
public Character getActivator() { return activator; }
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,77 @@
|
|||||||
|
package envoy.client.data.shortcuts;
|
||||||
|
|
||||||
|
import javafx.scene.input.*;
|
||||||
|
|
||||||
|
import envoy.data.User.UserStatus;
|
||||||
|
|
||||||
|
import envoy.client.data.Context;
|
||||||
|
import envoy.client.helper.ShutdownHelper;
|
||||||
|
import envoy.client.ui.SceneInfo;
|
||||||
|
import envoy.client.util.UserUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Envoy-specific implementation of the keyboard-shortcut interaction offered by
|
||||||
|
* {@link GlobalKeyShortcuts}.
|
||||||
|
*
|
||||||
|
* @author Leon Hofmeister
|
||||||
|
* @since Envoy Client v0.3-beta
|
||||||
|
*/
|
||||||
|
public class EnvoyShortcutConfig {
|
||||||
|
|
||||||
|
private EnvoyShortcutConfig() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Supplies the default shortcuts for {@link GlobalKeyShortcuts}.
|
||||||
|
*
|
||||||
|
* @since Envoy Client v0.3-beta
|
||||||
|
*/
|
||||||
|
public static void initializeEnvoyShortcuts() {
|
||||||
|
final var instance = GlobalKeyShortcuts.getInstance();
|
||||||
|
|
||||||
|
// Add the option to exit with "Control" + "Q" or "Alt" + "F4" as offered by
|
||||||
|
// some desktop environments
|
||||||
|
instance.add(new KeyCodeCombination(KeyCode.Q, KeyCombination.CONTROL_DOWN),
|
||||||
|
ShutdownHelper::exit);
|
||||||
|
|
||||||
|
// Add the option to logout using "Control"+"Shift"+"L" if not in login scene
|
||||||
|
instance.addForNotExcluded(
|
||||||
|
new KeyCodeCombination(KeyCode.L, KeyCombination.CONTROL_DOWN,
|
||||||
|
KeyCombination.SHIFT_DOWN),
|
||||||
|
UserUtil::logout,
|
||||||
|
SceneInfo.LOGIN_SCENE);
|
||||||
|
|
||||||
|
// Add option to open settings scene with "Control"+"S", if not in login scene
|
||||||
|
instance.addForNotExcluded(new KeyCodeCombination(KeyCode.S, KeyCombination.CONTROL_DOWN),
|
||||||
|
() -> Context.getInstance().getSceneContext().load(SceneInfo.SETTINGS_SCENE),
|
||||||
|
SceneInfo.SETTINGS_SCENE,
|
||||||
|
SceneInfo.LOGIN_SCENE);
|
||||||
|
|
||||||
|
// Add option to change to status away
|
||||||
|
instance.addForNotExcluded(
|
||||||
|
new KeyCodeCombination(KeyCode.A, KeyCombination.CONTROL_DOWN,
|
||||||
|
KeyCombination.SHIFT_DOWN),
|
||||||
|
() -> UserUtil.changeStatus(UserStatus.AWAY),
|
||||||
|
SceneInfo.LOGIN_SCENE);
|
||||||
|
|
||||||
|
// Add option to change to status busy
|
||||||
|
instance.addForNotExcluded(
|
||||||
|
new KeyCodeCombination(KeyCode.B, KeyCombination.CONTROL_DOWN,
|
||||||
|
KeyCombination.SHIFT_DOWN),
|
||||||
|
() -> UserUtil.changeStatus(UserStatus.BUSY),
|
||||||
|
SceneInfo.LOGIN_SCENE);
|
||||||
|
|
||||||
|
// Add option to change to status offline
|
||||||
|
instance.addForNotExcluded(
|
||||||
|
new KeyCodeCombination(KeyCode.F, KeyCombination.CONTROL_DOWN,
|
||||||
|
KeyCombination.SHIFT_DOWN),
|
||||||
|
() -> UserUtil.changeStatus(UserStatus.OFFLINE),
|
||||||
|
SceneInfo.LOGIN_SCENE);
|
||||||
|
|
||||||
|
// Add option to change to status online
|
||||||
|
instance.addForNotExcluded(
|
||||||
|
new KeyCodeCombination(KeyCode.N, KeyCombination.CONTROL_DOWN,
|
||||||
|
KeyCombination.SHIFT_DOWN),
|
||||||
|
() -> UserUtil.changeStatus(UserStatus.ONLINE),
|
||||||
|
SceneInfo.LOGIN_SCENE);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,79 @@
|
|||||||
|
package envoy.client.data.shortcuts;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import javafx.scene.input.KeyCombination;
|
||||||
|
|
||||||
|
import envoy.client.ui.SceneInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains all keyboard shortcuts used throughout the application.
|
||||||
|
*
|
||||||
|
* @author Leon Hofmeister
|
||||||
|
* @since Envoy Client v0.3-beta
|
||||||
|
*/
|
||||||
|
public final class GlobalKeyShortcuts {
|
||||||
|
|
||||||
|
private final EnumMap<SceneInfo, Map<KeyCombination, Runnable>> shortcuts =
|
||||||
|
new EnumMap<>(SceneInfo.class);
|
||||||
|
|
||||||
|
private static GlobalKeyShortcuts instance = new GlobalKeyShortcuts();
|
||||||
|
|
||||||
|
private GlobalKeyShortcuts() {
|
||||||
|
for (final var sceneInfo : SceneInfo.values())
|
||||||
|
shortcuts.put(sceneInfo, new HashMap<KeyCombination, Runnable>());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the instance of global keyboard shortcuts.
|
||||||
|
* @since Envoy Client v0.3-beta
|
||||||
|
*/
|
||||||
|
public static GlobalKeyShortcuts getInstance() { return instance; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the given keyboard shortcut and its action to all scenes.
|
||||||
|
*
|
||||||
|
* @param keys the keys to press to perform the given action
|
||||||
|
* @param action the action to perform
|
||||||
|
* @since Envoy Client v0.3-beta
|
||||||
|
*/
|
||||||
|
public void add(KeyCombination keys, Runnable action) {
|
||||||
|
shortcuts.values().forEach(collection -> collection.put(keys, action));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the given keyboard shortcut and its action to all scenes that are not part of exclude.
|
||||||
|
*
|
||||||
|
* @param keys the keys to press to perform the given action
|
||||||
|
* @param action the action to perform
|
||||||
|
* @param exclude the scenes that should be excluded from receiving this keyboard shortcut
|
||||||
|
* @since Envoy Client v0.3-beta
|
||||||
|
*/
|
||||||
|
public void addForNotExcluded(KeyCombination keys, Runnable action, SceneInfo... exclude) {
|
||||||
|
|
||||||
|
// Computing the remaining sceneInfos
|
||||||
|
final var include = new SceneInfo[SceneInfo.values().length - exclude.length];
|
||||||
|
int index = 0;
|
||||||
|
outer: for (final var sceneInfo : SceneInfo.values()) {
|
||||||
|
for (final var excluded : exclude)
|
||||||
|
if (sceneInfo.equals(excluded))
|
||||||
|
continue outer;
|
||||||
|
include[index++] = sceneInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adding the action to the remaining sceneInfos
|
||||||
|
for (final var sceneInfo : include)
|
||||||
|
shortcuts.get(sceneInfo).put(keys, action);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all stored keyboard shortcuts for the given scene constant.
|
||||||
|
*
|
||||||
|
* @param sceneInfo the currently loading scene
|
||||||
|
* @return all stored keyboard shortcuts for this scene
|
||||||
|
* @since Envoy Client v0.3-beta
|
||||||
|
*/
|
||||||
|
public Map<KeyCombination, Runnable> getKeyboardShortcuts(SceneInfo sceneInfo) {
|
||||||
|
return shortcuts.get(sceneInfo);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
package envoy.client.data.shortcuts;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javafx.scene.input.KeyCombination;
|
||||||
|
|
||||||
|
import envoy.client.ui.SceneContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides methods to set the keyboard shortcuts for a specific scene. Should only be implemented
|
||||||
|
* by controllers of scenes so that these methods can automatically be called inside
|
||||||
|
* {@link SceneContext} as soon as the underlying FXML file has been loaded.
|
||||||
|
*
|
||||||
|
* @author Leon Hofmeister
|
||||||
|
* @since Envoy Client v0.3-beta
|
||||||
|
*/
|
||||||
|
public interface KeyboardMapping {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return all keyboard shortcuts of a scene
|
||||||
|
* @since Envoy Client v0.3-beta
|
||||||
|
*/
|
||||||
|
Map<KeyCombination, Runnable> getKeyboardShortcuts();
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* Contains the necessary classes to enable using keyboard shortcuts in Envoy.
|
||||||
|
*
|
||||||
|
* @author Leon Hofmeister
|
||||||
|
* @since Envoy Client v0.3-beta
|
||||||
|
*/
|
||||||
|
package envoy.client.data.shortcuts;
|
22
client/src/main/java/envoy/client/event/AccountDeletion.java
Normal file
22
client/src/main/java/envoy/client/event/AccountDeletion.java
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package envoy.client.event;
|
||||||
|
|
||||||
|
import envoy.event.Event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signifies the deletion of an account.
|
||||||
|
*
|
||||||
|
* @author Leon Hofmeister
|
||||||
|
* @since Envoy Common v0.3-beta
|
||||||
|
*/
|
||||||
|
public class AccountDeletion extends Event<Long> {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param value the ID of the contact that was deleted
|
||||||
|
* @since Envoy Common v0.3-beta
|
||||||
|
*/
|
||||||
|
public AccountDeletion(Long value) {
|
||||||
|
super(value);
|
||||||
|
}
|
||||||
|
}
|
23
client/src/main/java/envoy/client/event/ContactDisabled.java
Normal file
23
client/src/main/java/envoy/client/event/ContactDisabled.java
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package envoy.client.event;
|
||||||
|
|
||||||
|
import envoy.data.Contact;
|
||||||
|
import envoy.event.Event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signifies that the chat of a contact should be disabled.
|
||||||
|
*
|
||||||
|
* @author Leon Hofmeister
|
||||||
|
* @since Envoy Client v0.3-beta
|
||||||
|
*/
|
||||||
|
public class ContactDisabled extends Event<Contact> {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param contact the contact that should be disabled
|
||||||
|
* @since Envoy Client v0.3-beta
|
||||||
|
*/
|
||||||
|
public ContactDisabled(Contact contact) {
|
||||||
|
super(contact);
|
||||||
|
}
|
||||||
|
}
|
@ -3,9 +3,8 @@ package envoy.client.event;
|
|||||||
import envoy.event.Event.Valueless;
|
import envoy.event.Event.Valueless;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This event notifies various Envoy components of the application being about
|
* This event notifies various Envoy components of the application being about to shut down. This
|
||||||
* to shut down. This allows the graceful closing of connections, persisting
|
* allows the graceful closing of connections, persisting local data etc.
|
||||||
* local data etc.
|
|
||||||
*
|
*
|
||||||
* @author Leon Hofmeister
|
* @author Leon Hofmeister
|
||||||
* @since Envoy Client v0.2-beta
|
* @since Envoy Client v0.2-beta
|
||||||
|
22
client/src/main/java/envoy/client/event/MessageDeletion.java
Normal file
22
client/src/main/java/envoy/client/event/MessageDeletion.java
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package envoy.client.event;
|
||||||
|
|
||||||
|
import envoy.event.Event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Conveys the deletion of a message.
|
||||||
|
*
|
||||||
|
* @author Leon Hofmeister
|
||||||
|
* @since Envoy Common v0.3-beta
|
||||||
|
*/
|
||||||
|
public class MessageDeletion extends Event<Long> {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param messageID the ID of the deleted message
|
||||||
|
* @since Envoy Common v0.3-beta
|
||||||
|
*/
|
||||||
|
public MessageDeletion(long messageID) {
|
||||||
|
super(messageID);
|
||||||
|
}
|
||||||
|
}
|
23
client/src/main/java/envoy/client/event/OwnStatusChange.java
Normal file
23
client/src/main/java/envoy/client/event/OwnStatusChange.java
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package envoy.client.event;
|
||||||
|
|
||||||
|
import envoy.data.User.UserStatus;
|
||||||
|
import envoy.event.Event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signifies a manual status change of the client user.
|
||||||
|
*
|
||||||
|
* @author Leon Hofmeister
|
||||||
|
* @since Envoy Client v0.3-beta
|
||||||
|
*/
|
||||||
|
public class OwnStatusChange extends Event<UserStatus> {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param value the new user status of the client user
|
||||||
|
* @since Envoy Client v0.3-beta
|
||||||
|
*/
|
||||||
|
public OwnStatusChange(UserStatus value) {
|
||||||
|
super(value);
|
||||||
|
}
|
||||||
|
}
|
@ -15,22 +15,19 @@ public final class AlertHelper {
|
|||||||
private AlertHelper() {}
|
private AlertHelper() {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Asks for a confirmation dialog if {@link Settings#isAskForConfirmation()}
|
* Asks for a confirmation dialog if {@link Settings#isAskForConfirmation()} returns
|
||||||
* returns {@code true}.
|
* {@code true}. Immediately executes the action if no dialog was requested or the dialog was
|
||||||
* Immediately executes the action if no dialog was requested or the dialog was
|
* exited with a confirmation. Does nothing if the dialog was closed without clicking on OK.
|
||||||
* exited with a confirmation.
|
|
||||||
* Does nothing if the dialog was closed without clicking on OK.
|
|
||||||
*
|
*
|
||||||
* @param alert the (customized) alert to show. <strong>Should not be shown
|
* @param alert the (customized) alert to show. <strong>Should not be shown already</strong>
|
||||||
* already</strong>
|
|
||||||
* @param action the action to perform in case of success
|
* @param action the action to perform in case of success
|
||||||
* @since Envoy Client v0.2-beta
|
* @since Envoy Client v0.2-beta
|
||||||
*/
|
*/
|
||||||
public static void confirmAction(Alert alert, Runnable action) {
|
public static void confirmAction(Alert alert, Runnable action) {
|
||||||
alert.setHeight(225);
|
|
||||||
alert.setWidth(400);
|
|
||||||
alert.setHeaderText("");
|
alert.setHeaderText("");
|
||||||
if (Settings.getInstance().isAskForConfirmation()) alert.showAndWait().filter(ButtonType.OK::equals).ifPresent(bu -> action.run());
|
if (Settings.getInstance().isAskForConfirmation())
|
||||||
else action.run();
|
alert.showAndWait().filter(ButtonType.OK::equals).ifPresent(bu -> action.run());
|
||||||
|
else
|
||||||
|
action.run();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,10 @@
|
|||||||
package envoy.client.helper;
|
package envoy.client.helper;
|
||||||
|
|
||||||
import java.util.logging.Level;
|
import dev.kske.eventbus.core.EventBus;
|
||||||
|
|
||||||
import javafx.scene.control.Alert;
|
|
||||||
import javafx.scene.control.Alert.AlertType;
|
|
||||||
|
|
||||||
import envoy.client.data.*;
|
import envoy.client.data.*;
|
||||||
import envoy.client.event.*;
|
import envoy.client.event.EnvoyCloseEvent;
|
||||||
import envoy.client.ui.SceneContext.SceneInfo;
|
import envoy.client.ui.StatusTrayIcon;
|
||||||
import envoy.util.EnvoyLog;
|
|
||||||
|
|
||||||
import dev.kske.eventbus.EventBus;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Simplifies shutdown actions.
|
* Simplifies shutdown actions.
|
||||||
@ -24,34 +18,28 @@ public final class ShutdownHelper {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Exits Envoy or minimizes it, depending on the current state of
|
* Exits Envoy or minimizes it, depending on the current state of
|
||||||
* {@link Settings#isHideOnClose()}.
|
* {@link Settings#isHideOnClose()} and {@link StatusTrayIcon#isSupported()}.
|
||||||
*
|
*
|
||||||
* @since Envoy Client v0.2-beta
|
* @since Envoy Client v0.2-beta
|
||||||
*/
|
*/
|
||||||
public static void exit() {
|
public static void exit() {
|
||||||
if (Settings.getInstance().isHideOnClose()) Context.getInstance().getStage().setIconified(true);
|
exit(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exits Envoy immediately if {@code force = true}, else it can exit or minimize Envoy,
|
||||||
|
* depending on the current state of {@link Settings#isHideOnClose()} and
|
||||||
|
* {@link StatusTrayIcon#isSupported()}.
|
||||||
|
*
|
||||||
|
* @param force whether to close in any case.
|
||||||
|
* @since Envoy Client v0.2-beta
|
||||||
|
*/
|
||||||
|
public static void exit(boolean force) {
|
||||||
|
if (!force && Settings.getInstance().isHideOnClose() && StatusTrayIcon.isSupported())
|
||||||
|
Context.getInstance().getStage().setIconified(true);
|
||||||
else {
|
else {
|
||||||
EventBus.getInstance().dispatch(new EnvoyCloseEvent());
|
EventBus.getInstance().dispatch(new EnvoyCloseEvent());
|
||||||
System.exit(0);
|
System.exit(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Logs the current user out and reopens
|
|
||||||
* {@link envoy.client.ui.controller.LoginScene}.
|
|
||||||
*
|
|
||||||
* @since Envoy Client v0.2-beta
|
|
||||||
*/
|
|
||||||
public static void logout() {
|
|
||||||
final var alert = new Alert(AlertType.CONFIRMATION);
|
|
||||||
alert.setTitle("Logout?");
|
|
||||||
alert.setContentText("Are you sure you want to log out?");
|
|
||||||
|
|
||||||
AlertHelper.confirmAction(alert, () -> {
|
|
||||||
EnvoyLog.getLogger(ShutdownHelper.class).log(Level.INFO, "A logout was requested");
|
|
||||||
EventBus.getInstance().dispatch(new EnvoyCloseEvent());
|
|
||||||
EventBus.getInstance().dispatch(new Logout());
|
|
||||||
Context.getInstance().getSceneContext().load(SceneInfo.LOGIN_SCENE);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -5,25 +5,26 @@ import java.net.Socket;
|
|||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
import java.util.logging.*;
|
import java.util.logging.*;
|
||||||
|
|
||||||
import envoy.client.data.*;
|
import dev.kske.eventbus.core.*;
|
||||||
import envoy.client.event.EnvoyCloseEvent;
|
import dev.kske.eventbus.core.Event;
|
||||||
|
|
||||||
import envoy.data.*;
|
import envoy.data.*;
|
||||||
import envoy.event.*;
|
import envoy.event.*;
|
||||||
import envoy.util.*;
|
import envoy.util.*;
|
||||||
|
|
||||||
import dev.kske.eventbus.*;
|
import envoy.client.data.ClientConfig;
|
||||||
import dev.kske.eventbus.Event;
|
import envoy.client.event.EnvoyCloseEvent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Establishes a connection to the server, performs a handshake and delivers
|
* Establishes a connection to the server, performs a handshake and delivers certain objects to the
|
||||||
* certain objects to the server.
|
* server.
|
||||||
*
|
*
|
||||||
* @author Kai S. K. Engelbart
|
* @author Kai S. K. Engelbart
|
||||||
* @author Maximilian Käfer
|
* @author Maximilian Käfer
|
||||||
* @author Leon Hofmeister
|
* @author Leon Hofmeister
|
||||||
* @since Envoy Client v0.1-alpha
|
* @since Envoy Client v0.1-alpha
|
||||||
*/
|
*/
|
||||||
public final class Client implements EventListener, Closeable {
|
public final class Client implements Closeable {
|
||||||
|
|
||||||
// Connection handling
|
// Connection handling
|
||||||
private Socket socket;
|
private Socket socket;
|
||||||
@ -44,26 +45,30 @@ public final class Client implements EventListener, Closeable {
|
|||||||
*
|
*
|
||||||
* @since Envoy Client v0.2-beta
|
* @since Envoy Client v0.2-beta
|
||||||
*/
|
*/
|
||||||
public Client() { eventBus.registerListener(this); }
|
public Client() {
|
||||||
|
eventBus.registerListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enters the online mode by acquiring a user ID from the server. As a
|
* Enters the online mode by acquiring a user ID from the server. As a connection has to be
|
||||||
* connection has to be established and a handshake has to be made, this method
|
* established and a handshake has to be made, this method will block for up to 5 seconds. If
|
||||||
* will block for up to 5 seconds. If the handshake does exceed this time limit,
|
* the handshake does exceed this time limit, an exception is thrown.
|
||||||
* an exception is thrown.
|
|
||||||
*
|
*
|
||||||
* @param credentials the login credentials of the user
|
* @param credentials the login credentials of the user
|
||||||
* @param cacheMap the map of all caches needed
|
|
||||||
* @throws TimeoutException if the server could not be reached
|
* @throws TimeoutException if the server could not be reached
|
||||||
* @throws IOException if the login credentials could not be written
|
* @throws IOException if the login credentials could not be written
|
||||||
* @throws InterruptedException if the current thread is interrupted while
|
* @throws InterruptedException if the current thread is interrupted while waiting for the
|
||||||
* waiting for the handshake response
|
* handshake response
|
||||||
*/
|
*/
|
||||||
public void performHandshake(LoginCredentials credentials, CacheMap cacheMap) throws TimeoutException, IOException, InterruptedException {
|
public void performHandshake(LoginCredentials credentials)
|
||||||
if (online) throw new IllegalStateException("Handshake has already been performed successfully");
|
throws TimeoutException, IOException, InterruptedException {
|
||||||
|
if (online)
|
||||||
|
throw new IllegalStateException("Handshake has already been performed successfully");
|
||||||
|
rejected = false;
|
||||||
|
|
||||||
// Establish TCP connection
|
// Establish TCP connection
|
||||||
logger.log(Level.FINER, String.format("Attempting connection to server %s:%d...", config.getServer(), config.getPort()));
|
logger.log(Level.FINER, String.format("Attempting connection to server %s:%d...",
|
||||||
|
config.getServer(), config.getPort()));
|
||||||
socket = new Socket(config.getServer(), config.getPort());
|
socket = new Socket(config.getServer(), config.getPort());
|
||||||
logger.log(Level.FINE, "Successfully established TCP connection to server");
|
logger.log(Level.FINE, "Successfully established TCP connection to server");
|
||||||
|
|
||||||
@ -73,9 +78,6 @@ public final class Client implements EventListener, Closeable {
|
|||||||
// Register user creation processor, contact list processor, message cache and
|
// Register user creation processor, contact list processor, message cache and
|
||||||
// authentication token
|
// authentication token
|
||||||
receiver.registerProcessor(User.class, sender -> this.sender = sender);
|
receiver.registerProcessor(User.class, sender -> this.sender = sender);
|
||||||
receiver.registerProcessors(cacheMap.getMap());
|
|
||||||
|
|
||||||
rejected = false;
|
|
||||||
|
|
||||||
// Start receiver
|
// Start receiver
|
||||||
receiver.start();
|
receiver.start();
|
||||||
@ -95,42 +97,20 @@ public final class Client implements EventListener, Closeable {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (System.currentTimeMillis() - start > 5000) throw new TimeoutException("Did not log in after 5 seconds");
|
if (System.currentTimeMillis() - start > 5000) {
|
||||||
|
rejected = true;
|
||||||
|
socket.close();
|
||||||
|
receiver.removeAllProcessors();
|
||||||
|
throw new TimeoutException("Did not log in after 5 seconds");
|
||||||
|
}
|
||||||
Thread.sleep(500);
|
Thread.sleep(500);
|
||||||
}
|
}
|
||||||
|
|
||||||
online = true;
|
// Remove handshake specific processors
|
||||||
logger.log(Level.INFO, "Handshake completed.");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the {@link Receiver} used to process data sent from the server to
|
|
||||||
* this client.
|
|
||||||
*
|
|
||||||
* @param localDB the local database used to persist the current
|
|
||||||
* {@link IDGenerator}
|
|
||||||
* @param cacheMap the map of all caches needed
|
|
||||||
* @throws IOException if no {@link IDGenerator} is present and none could be
|
|
||||||
* requested from the server
|
|
||||||
* @since Envoy Client v0.2-alpha
|
|
||||||
*/
|
|
||||||
public void initReceiver(LocalDB localDB, CacheMap cacheMap) throws IOException {
|
|
||||||
checkOnline();
|
|
||||||
|
|
||||||
// Remove all processors as they are only used during the handshake
|
|
||||||
receiver.removeAllProcessors();
|
receiver.removeAllProcessors();
|
||||||
|
|
||||||
// Relay cached messages and message status changes
|
online = true;
|
||||||
cacheMap.get(Message.class).setProcessor(eventBus::dispatch);
|
logger.log(Level.INFO, "Handshake completed.");
|
||||||
cacheMap.get(GroupMessage.class).setProcessor(eventBus::dispatch);
|
|
||||||
cacheMap.get(MessageStatusChange.class).setProcessor(eventBus::dispatch);
|
|
||||||
cacheMap.get(GroupMessageStatusChange.class).setProcessor(eventBus::dispatch);
|
|
||||||
|
|
||||||
// Request a generator if none is present or the existing one is consumed
|
|
||||||
if (!localDB.hasIDGenerator() || !localDB.getIDGenerator().hasNext()) requestIDGenerator();
|
|
||||||
|
|
||||||
// Relay caches
|
|
||||||
cacheMap.getMap().values().forEach(Cache::relay);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -146,14 +126,14 @@ public final class Client implements EventListener, Closeable {
|
|||||||
logger.log(Level.FINE, "Sending " + obj);
|
logger.log(Level.FINE, "Sending " + obj);
|
||||||
try {
|
try {
|
||||||
SerializationUtils.writeBytesWithLength(obj, socket.getOutputStream());
|
SerializationUtils.writeBytesWithLength(obj, socket.getOutputStream());
|
||||||
} catch (IOException e) {
|
} catch (final IOException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends a message to the server. The message's status will be incremented once
|
* Sends a message to the server. The message's status will be incremented once it was delivered
|
||||||
* it was delivered successfully.
|
* successfully.
|
||||||
*
|
*
|
||||||
* @param message the message to send
|
* @param message the message to send
|
||||||
* @since Envoy Client v0.3-alpha
|
* @since Envoy Client v0.3-alpha
|
||||||
@ -173,11 +153,15 @@ public final class Client implements EventListener, Closeable {
|
|||||||
send(new IDGeneratorRequest());
|
send(new IDGeneratorRequest());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Event(eventType = HandshakeRejection.class, priority = 1000)
|
@Event(HandshakeRejection.class)
|
||||||
private void onHandshakeRejection() { rejected = true; }
|
@Priority(1000)
|
||||||
|
private void onHandshakeRejection() {
|
||||||
|
rejected = true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Event(eventType = EnvoyCloseEvent.class, priority = 800)
|
@Event(EnvoyCloseEvent.class)
|
||||||
|
@Priority(50)
|
||||||
public void close() {
|
public void close() {
|
||||||
if (online) {
|
if (online) {
|
||||||
logger.log(Level.INFO, "Closing connection...");
|
logger.log(Level.INFO, "Closing connection...");
|
||||||
@ -199,7 +183,10 @@ public final class Client implements EventListener, Closeable {
|
|||||||
* @throws IllegalStateException if the client is not online
|
* @throws IllegalStateException if the client is not online
|
||||||
* @since Envoy Client v0.3-alpha
|
* @since Envoy Client v0.3-alpha
|
||||||
*/
|
*/
|
||||||
private void checkOnline() throws IllegalStateException { if (!online) throw new IllegalStateException("Client is not online"); }
|
private void checkOnline() throws IllegalStateException {
|
||||||
|
if (!online)
|
||||||
|
throw new IllegalStateException("Client is not online");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the {@link User} as which this client is logged in
|
* @return the {@link User} as which this client is logged in
|
||||||
|
@ -6,13 +6,12 @@ import java.util.*;
|
|||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.logging.*;
|
import java.util.logging.*;
|
||||||
|
|
||||||
|
import dev.kske.eventbus.core.EventBus;
|
||||||
|
|
||||||
import envoy.util.*;
|
import envoy.util.*;
|
||||||
|
|
||||||
import dev.kske.eventbus.*;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Receives objects from the server and passes them to processor objects based
|
* Receives objects from the server and passes them to processor objects based on their class.
|
||||||
* on their class.
|
|
||||||
*
|
*
|
||||||
* @author Kai S. K. Engelbart
|
* @author Kai S. K. Engelbart
|
||||||
* @since Envoy Client v0.3-alpha
|
* @since Envoy Client v0.3-alpha
|
||||||
@ -40,8 +39,7 @@ public final class Receiver extends Thread {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts the receiver loop. When an object is read, it is passed to the
|
* Starts the receiver loop. When an object is read, it is passed to the appropriate processor.
|
||||||
* appropriate processor.
|
|
||||||
*
|
*
|
||||||
* @since Envoy Client v0.3-alpha
|
* @since Envoy Client v0.3-alpha
|
||||||
*/
|
*/
|
||||||
@ -66,15 +64,19 @@ public final class Receiver extends Thread {
|
|||||||
// Server has stopped sending, i.e. because he went offline
|
// Server has stopped sending, i.e. because he went offline
|
||||||
if (bytesRead == -1) {
|
if (bytesRead == -1) {
|
||||||
isAlive = false;
|
isAlive = false;
|
||||||
logger.log(Level.INFO, "Lost connection to the server. Exiting receiver...");
|
logger.log(Level.INFO,
|
||||||
|
"Lost connection to the server. Exiting receiver...");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
logger.log(Level.WARNING,
|
logger.log(Level.WARNING,
|
||||||
String.format("LV encoding violated: expected %d bytes, received %d bytes. Discarding object...", len, bytesRead));
|
String.format(
|
||||||
|
"LV encoding violated: expected %d bytes, received %d bytes. Discarding object...",
|
||||||
|
len, bytesRead));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
try (ObjectInputStream oin = new ObjectInputStream(new ByteArrayInputStream(objBytes))) {
|
try (ObjectInputStream oin =
|
||||||
|
new ObjectInputStream(new ByteArrayInputStream(objBytes))) {
|
||||||
final Object obj = oin.readObject();
|
final Object obj = oin.readObject();
|
||||||
logger.log(Level.FINE, "Received " + obj);
|
logger.log(Level.FINE, "Received " + obj);
|
||||||
|
|
||||||
@ -83,12 +85,19 @@ public final class Receiver extends Thread {
|
|||||||
final Consumer processor = processors.get(obj.getClass());
|
final Consumer processor = processors.get(obj.getClass());
|
||||||
|
|
||||||
// Dispatch to the processor if present
|
// Dispatch to the processor if present
|
||||||
if (processor != null) processor.accept(obj);
|
if (processor != null)
|
||||||
// Dispatch to the event bus if the object is an event without a processor
|
processor.accept(obj);
|
||||||
else if (obj instanceof IEvent) eventBus.dispatch((IEvent) obj);
|
// Dispatch to the event bus if the object has no processor
|
||||||
|
else
|
||||||
|
eventBus.dispatch(obj);
|
||||||
|
|
||||||
|
// TODO: Log DeadEvent from Event Bus 1.1.0
|
||||||
// Notify if no processor could be located
|
// Notify if no processor could be located
|
||||||
else logger.log(Level.WARNING,
|
// else
|
||||||
String.format("The received object has the %s for which no processor is defined.", obj.getClass()));
|
// logger.log(Level.WARNING,
|
||||||
|
// String.format(
|
||||||
|
// "The received object has the %s for which no processor is defined.",
|
||||||
|
// obj.getClass()));
|
||||||
}
|
}
|
||||||
} catch (final SocketException | EOFException e) {
|
} catch (final SocketException | EOFException e) {
|
||||||
// Connection probably closed by client.
|
// Connection probably closed by client.
|
||||||
@ -100,14 +109,16 @@ public final class Receiver extends Thread {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds an object processor to this {@link Receiver}. It will be called once an
|
* Adds an object processor to this {@link Receiver}. It will be called once an object of the
|
||||||
* object of the accepted class has been received.
|
* accepted class has been received.
|
||||||
*
|
*
|
||||||
* @param processorClass the object class accepted by the processor
|
* @param processorClass the object class accepted by the processor
|
||||||
* @param processor the object processor
|
* @param processor the object processor
|
||||||
* @since Envoy Client v0.3-alpha
|
* @since Envoy Client v0.3-alpha
|
||||||
*/
|
*/
|
||||||
public <T> void registerProcessor(Class<T> processorClass, Consumer<T> processor) { processors.put(processorClass, processor); }
|
public <T> void registerProcessor(Class<T> processorClass, Consumer<T> processor) {
|
||||||
|
processors.put(processorClass, processor);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a map of object processors to this {@link Receiver}.
|
* Adds a map of object processors to this {@link Receiver}.
|
||||||
@ -115,12 +126,16 @@ public final class Receiver extends Thread {
|
|||||||
* @param processors the processors to add the processors to add
|
* @param processors the processors to add the processors to add
|
||||||
* @since Envoy Client v0.1-beta
|
* @since Envoy Client v0.1-beta
|
||||||
*/
|
*/
|
||||||
public void registerProcessors(Map<Class<?>, ? extends Consumer<?>> processors) { this.processors.putAll(processors); }
|
public void registerProcessors(Map<Class<?>, ? extends Consumer<?>> processors) {
|
||||||
|
this.processors.putAll(processors);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes all object processors registered at this {@link Receiver}.
|
* Removes all object processors registered at this {@link Receiver}.
|
||||||
*
|
*
|
||||||
* @since Envoy Client v0.3-alpha
|
* @since Envoy Client v0.3-alpha
|
||||||
*/
|
*/
|
||||||
public void removeAllProcessors() { processors.clear(); }
|
public void removeAllProcessors() {
|
||||||
|
processors.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,15 +2,15 @@ package envoy.client.net;
|
|||||||
|
|
||||||
import java.util.logging.*;
|
import java.util.logging.*;
|
||||||
|
|
||||||
import envoy.client.data.*;
|
|
||||||
import envoy.data.Message;
|
import envoy.data.Message;
|
||||||
import envoy.event.MessageStatusChange;
|
import envoy.event.MessageStatusChange;
|
||||||
import envoy.util.EnvoyLog;
|
import envoy.util.EnvoyLog;
|
||||||
|
|
||||||
|
import envoy.client.data.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements methods to send {@link Message}s and
|
* Implements methods to send {@link Message}s and {@link MessageStatusChange}s to the server or
|
||||||
* {@link MessageStatusChange}s to the server or cache them inside a
|
* cache them inside a {@link LocalDB} depending on the online status.
|
||||||
* {@link LocalDB} depending on the online status.
|
|
||||||
*
|
*
|
||||||
* @author Kai S. K. Engelbart
|
* @author Kai S. K. Engelbart
|
||||||
* @since Envoy Client v0.3-alpha
|
* @since Envoy Client v0.3-alpha
|
||||||
@ -23,12 +23,11 @@ public final class WriteProxy {
|
|||||||
private static final Logger logger = EnvoyLog.getLogger(WriteProxy.class);
|
private static final Logger logger = EnvoyLog.getLogger(WriteProxy.class);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes a write proxy using a client and a local database. The
|
* Initializes a write proxy using a client and a local database. The corresponding cache
|
||||||
* corresponding cache processors are injected into the caches.
|
* processors are injected into the caches.
|
||||||
*
|
*
|
||||||
* @param client the client instance used to send messages and events if online
|
* @param client the client instance used to send messages and events if online
|
||||||
* @param localDB the local database used to cache messages and events if
|
* @param localDB the local database used to cache messages and events if offline
|
||||||
* offline
|
|
||||||
* @since Envoy Client v0.3-alpha
|
* @since Envoy Client v0.3-alpha
|
||||||
*/
|
*/
|
||||||
public WriteProxy(Client client, LocalDB localDB) {
|
public WriteProxy(Client client, LocalDB localDB) {
|
||||||
@ -47,34 +46,39 @@ public final class WriteProxy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends cached {@link Message}s and {@link MessageStatusChange}s to the
|
* Sends cached {@link Message}s and {@link MessageStatusChange}s to the server.
|
||||||
* server.
|
|
||||||
*
|
*
|
||||||
* @since Envoy Client v0.3-alpha
|
* @since Envoy Client v0.3-alpha
|
||||||
*/
|
*/
|
||||||
public void flushCache() { localDB.getCacheMap().getMap().values().forEach(Cache::relay); }
|
public void flushCache() {
|
||||||
|
localDB.getCacheMap().getMap().values().forEach(Cache::relay);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delivers a message to the server if online. Otherwise the message is cached
|
* Delivers a message to the server if online. Otherwise the message is cached inside the local
|
||||||
* inside the local database.
|
* database.
|
||||||
*
|
*
|
||||||
* @param message the message to send
|
* @param message the message to send
|
||||||
* @since Envoy Client v0.3-alpha
|
* @since Envoy Client v0.3-alpha
|
||||||
*/
|
*/
|
||||||
public void writeMessage(Message message) {
|
public void writeMessage(Message message) {
|
||||||
if (client.isOnline()) client.sendMessage(message);
|
if (client.isOnline())
|
||||||
else localDB.getCacheMap().getApplicable(Message.class).accept(message);
|
client.sendMessage(message);
|
||||||
|
else
|
||||||
|
localDB.getCacheMap().getApplicable(Message.class).accept(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delivers a message status change event to the server if online. Otherwise the
|
* Delivers a message status change event to the server if online. Otherwise the event is cached
|
||||||
* event is cached inside the local database.
|
* inside the local database.
|
||||||
*
|
*
|
||||||
* @param evt the event to send
|
* @param evt the event to send
|
||||||
* @since Envoy Client v0.3-alpha
|
* @since Envoy Client v0.3-alpha
|
||||||
*/
|
*/
|
||||||
public void writeMessageStatusChange(MessageStatusChange evt) {
|
public void writeMessageStatusChange(MessageStatusChange evt) {
|
||||||
if (client.isOnline()) client.send(evt);
|
if (client.isOnline())
|
||||||
else localDB.getCacheMap().getApplicable(MessageStatusChange.class).accept(evt);
|
client.send(evt);
|
||||||
|
else
|
||||||
|
localDB.getCacheMap().getApplicable(MessageStatusChange.class).accept(evt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package envoy.client.ui;
|
package envoy.client.ui;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This interface defines an action that should be performed when a scene gets
|
* This interface defines an action that should be performed when a scene gets restored from the
|
||||||
* restored from the scene stack in {@link SceneContext}.
|
* scene stack in {@link SceneContext}.
|
||||||
*
|
*
|
||||||
* @author Leon Hofmeister
|
* @author Leon Hofmeister
|
||||||
* @since Envoy Client v0.1-beta
|
* @since Envoy Client v0.1-beta
|
||||||
@ -12,8 +12,7 @@ public interface Restorable {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* This method is getting called when a scene gets restored.<br>
|
* This method is getting called when a scene gets restored.<br>
|
||||||
* Hence, it can contain anything that should be done when the underlying scene
|
* Hence, it can contain anything that should be done when the underlying scene gets restored.
|
||||||
* gets restored.
|
|
||||||
*
|
*
|
||||||
* @since Envoy Client v0.1-beta
|
* @since Envoy Client v0.1-beta
|
||||||
*/
|
*/
|
||||||
|
@ -4,75 +4,34 @@ import java.io.IOException;
|
|||||||
import java.util.Stack;
|
import java.util.Stack;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
|
||||||
import javafx.application.Platform;
|
|
||||||
import javafx.fxml.FXMLLoader;
|
import javafx.fxml.FXMLLoader;
|
||||||
import javafx.scene.*;
|
import javafx.scene.*;
|
||||||
import javafx.scene.input.*;
|
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
|
|
||||||
import envoy.client.data.Settings;
|
import dev.kske.eventbus.core.*;
|
||||||
import envoy.client.event.*;
|
|
||||||
import envoy.client.helper.ShutdownHelper;
|
|
||||||
import envoy.util.EnvoyLog;
|
import envoy.util.EnvoyLog;
|
||||||
|
|
||||||
import dev.kske.eventbus.*;
|
import envoy.client.data.Settings;
|
||||||
|
import envoy.client.data.shortcuts.*;
|
||||||
|
import envoy.client.event.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages a stack of scenes. The most recently added scene is displayed inside
|
* Manages a stack of scenes. The most recently added scene is displayed inside a stage. When a
|
||||||
* a stage. When a scene is removed from the stack, its predecessor is
|
* scene is removed from the stack, its predecessor is displayed.
|
||||||
* displayed.
|
|
||||||
* <p>
|
* <p>
|
||||||
* When a scene is loaded, the style sheet for the current theme is applied to
|
* When a scene is loaded, the style sheet for the current theme is applied to it.
|
||||||
* it.
|
|
||||||
*
|
*
|
||||||
* @author Kai S. K. Engelbart
|
* @author Kai S. K. Engelbart
|
||||||
* @since Envoy Client v0.1-beta
|
* @since Envoy Client v0.1-beta
|
||||||
*/
|
*/
|
||||||
public final class SceneContext implements EventListener {
|
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 enum SceneInfo {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The main scene in which the chat screen is displayed.
|
|
||||||
*
|
|
||||||
* @since Envoy Client v0.1-beta
|
|
||||||
*/
|
|
||||||
CHAT_SCENE("/fxml/ChatScene.fxml"),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The scene in which the settings screen is displayed.
|
|
||||||
*
|
|
||||||
* @since Envoy Client v0.1-beta
|
|
||||||
*/
|
|
||||||
SETTINGS_SCENE("/fxml/SettingsScene.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 Stage stage;
|
||||||
private final FXMLLoader loader = new FXMLLoader();
|
private final Stack<Parent> roots = new Stack<>();
|
||||||
private final Stack<Scene> sceneStack = new Stack<>();
|
private final Stack<Object> controllers = new Stack<>();
|
||||||
private final Stack<Object> controllerStack = new Stack<>();
|
|
||||||
|
|
||||||
private static final Settings settings = Settings.getInstance();
|
private Scene scene;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the scene context.
|
* Initializes the scene context.
|
||||||
@ -88,44 +47,44 @@ public final class SceneContext implements EventListener {
|
|||||||
/**
|
/**
|
||||||
* Loads a new scene specified by a scene info.
|
* Loads a new scene specified by a scene info.
|
||||||
*
|
*
|
||||||
* @param sceneInfo specifies the scene to load
|
* @param info specifies the scene to load
|
||||||
* @throws RuntimeException if the loading process fails
|
* @throws RuntimeException if the loading process fails
|
||||||
* @since Envoy Client v0.1-beta
|
* @since Envoy Client v0.1-beta
|
||||||
*/
|
*/
|
||||||
public void load(SceneInfo sceneInfo) {
|
public void load(SceneInfo info) {
|
||||||
EnvoyLog.getLogger(SceneContext.class).log(Level.FINER, "Loading scene " + sceneInfo);
|
EnvoyLog.getLogger(SceneContext.class).log(Level.FINER, "Loading scene " + info);
|
||||||
loader.setRoot(null);
|
|
||||||
loader.setController(null);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final var rootNode = (Parent) loader.load(getClass().getResourceAsStream(sceneInfo.path));
|
|
||||||
final var scene = new Scene(rootNode);
|
|
||||||
controllerStack.push(loader.getController());
|
|
||||||
|
|
||||||
sceneStack.push(scene);
|
// Load root node and controller
|
||||||
stage.setScene(scene);
|
var loader = new FXMLLoader();
|
||||||
|
Parent root = loader.load(getClass().getResourceAsStream(info.path));
|
||||||
|
Object controller = loader.getController();
|
||||||
|
roots.push(root);
|
||||||
|
controllers.push(controller);
|
||||||
|
|
||||||
// Add the option to exit Linux-like with "Control" + "Q"
|
if (scene == null) {
|
||||||
scene.getAccelerators().put(new KeyCodeCombination(KeyCode.Q, KeyCombination.CONTROL_DOWN), ShutdownHelper::exit);
|
|
||||||
|
|
||||||
// Add the option to logout using "Control"+"Shift"+"L" if not in login scene
|
// One-time scene initialization
|
||||||
if (sceneInfo != SceneInfo.LOGIN_SCENE) scene.getAccelerators()
|
scene = new Scene(root, stage.getWidth(), stage.getHeight());
|
||||||
.put(new KeyCodeCombination(KeyCode.L, KeyCombination.CONTROL_DOWN, KeyCombination.SHIFT_DOWN), ShutdownHelper::logout);
|
|
||||||
|
|
||||||
// Add the option to open the settings scene with "Control"+"S", if being in
|
|
||||||
// chat scene
|
|
||||||
if (sceneInfo.equals(SceneInfo.CHAT_SCENE))
|
|
||||||
scene.getAccelerators().put(new KeyCodeCombination(KeyCode.S, KeyCombination.CONTROL_DOWN), () -> load(SceneInfo.SETTINGS_SCENE));
|
|
||||||
|
|
||||||
// The LoginScene is the only scene not intended to be resized
|
|
||||||
// As strange as it seems, this is needed as otherwise the LoginScene won't be
|
|
||||||
// displayed on some OS (...Debian...)
|
|
||||||
stage.sizeToScene();
|
|
||||||
Platform.runLater(() -> stage.setResizable(sceneInfo != SceneInfo.LOGIN_SCENE));
|
|
||||||
applyCSS();
|
applyCSS();
|
||||||
stage.show();
|
stage.setScene(scene);
|
||||||
} catch (final IOException e) {
|
} else {
|
||||||
EnvoyLog.getLogger(SceneContext.class).log(Level.SEVERE, String.format("Could not load scene for %s: ", sceneInfo), e);
|
scene.setRoot(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove previous keyboard shortcuts
|
||||||
|
scene.getAccelerators().clear();
|
||||||
|
|
||||||
|
// Supply the global custom keyboard shortcuts for that scene
|
||||||
|
scene.getAccelerators()
|
||||||
|
.putAll(GlobalKeyShortcuts.getInstance().getKeyboardShortcuts(info));
|
||||||
|
|
||||||
|
// Supply the scene specific keyboard shortcuts
|
||||||
|
if (controller instanceof KeyboardMapping)
|
||||||
|
scene.getAccelerators()
|
||||||
|
.putAll(((KeyboardMapping) controller).getKeyboardShortcuts());
|
||||||
|
} catch (IOException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -137,48 +96,55 @@ public final class SceneContext implements EventListener {
|
|||||||
*/
|
*/
|
||||||
public void pop() {
|
public void pop() {
|
||||||
|
|
||||||
// Pop scene and controller
|
// Pop current root node and controller
|
||||||
sceneStack.pop();
|
roots.pop();
|
||||||
controllerStack.pop();
|
controllers.pop();
|
||||||
|
|
||||||
// Apply new scene if present
|
// Apply new scene if present
|
||||||
if (!sceneStack.isEmpty()) {
|
if (!roots.isEmpty()) {
|
||||||
final var newScene = sceneStack.peek();
|
scene.setRoot(roots.peek());
|
||||||
stage.setScene(newScene);
|
|
||||||
applyCSS();
|
// Invoke restore if controller is restorable
|
||||||
stage.sizeToScene();
|
var controller = controllers.peek();
|
||||||
// If the controller implements the Restorable interface,
|
if (controller instanceof Restorable)
|
||||||
// the actions to perform on restoration will be executed here
|
((Restorable) controller).onRestore();
|
||||||
final var controller = controllerStack.peek();
|
} else {
|
||||||
if (controller instanceof Restorable) ((Restorable) controller).onRestore();
|
|
||||||
|
// Remove the current scene entirely
|
||||||
|
scene = null;
|
||||||
|
stage.setScene(null);
|
||||||
}
|
}
|
||||||
stage.show();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void applyCSS() {
|
private void applyCSS() {
|
||||||
if (!sceneStack.isEmpty()) {
|
if (scene != null) {
|
||||||
final var styleSheets = stage.getScene().getStylesheets();
|
var styleSheets = scene.getStylesheets();
|
||||||
final var themeCSS = "/css/" + settings.getCurrentTheme() + ".css";
|
var themeCSS = "/css/" + Settings.getInstance().getCurrentTheme() + ".css";
|
||||||
styleSheets.clear();
|
styleSheets.clear();
|
||||||
styleSheets.addAll(getClass().getResource("/css/base.css").toExternalForm(), getClass().getResource(themeCSS).toExternalForm());
|
styleSheets.addAll(getClass().getResource("/css/base.css").toExternalForm(),
|
||||||
|
getClass().getResource(themeCSS).toExternalForm());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Event(eventType = Logout.class, priority = 150)
|
@Event(Logout.class)
|
||||||
|
@Priority(150)
|
||||||
private void onLogout() {
|
private void onLogout() {
|
||||||
sceneStack.clear();
|
roots.clear();
|
||||||
controllerStack.clear();
|
controllers.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Event(priority = 150, eventType = ThemeChangeEvent.class)
|
@Event(ThemeChangeEvent.class)
|
||||||
private void onThemeChange() { applyCSS(); }
|
@Priority(150)
|
||||||
|
private void onThemeChange() {
|
||||||
|
applyCSS();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param <T> the type of the controller
|
* @param <T> the type of the controller
|
||||||
* @return the controller used by the current scene
|
* @return the controller used by the current scene
|
||||||
* @since Envoy Client v0.1-beta
|
* @since Envoy Client v0.1-beta
|
||||||
*/
|
*/
|
||||||
public <T> T getController() { return (T) controllerStack.peek(); }
|
public <T> T getController() { return (T) controllers.peek(); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the stage in which the scenes are displayed
|
* @return the stage in which the scenes are displayed
|
||||||
@ -190,5 +156,5 @@ public final class SceneContext implements EventListener {
|
|||||||
* @return whether the scene stack is empty
|
* @return whether the scene stack is empty
|
||||||
* @since Envoy Client v0.2-beta
|
* @since Envoy Client v0.2-beta
|
||||||
*/
|
*/
|
||||||
public boolean isEmpty() { return sceneStack.isEmpty(); }
|
public boolean isEmpty() { return roots.isEmpty(); }
|
||||||
}
|
}
|
||||||
|
40
client/src/main/java/envoy/client/ui/SceneInfo.java
Normal file
40
client/src/main/java/envoy/client/ui/SceneInfo.java
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
package envoy.client.ui;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains information about different scenes and their FXML resource files.
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public enum SceneInfo {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The main scene in which the chat screen is displayed.
|
||||||
|
*
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
CHAT_SCENE("/fxml/ChatScene.fxml"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The scene in which the settings screen is displayed.
|
||||||
|
*
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
SETTINGS_SCENE("/fxml/SettingsScene.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;
|
||||||
|
}
|
||||||
|
}
|
@ -10,18 +10,19 @@ import javafx.scene.control.Alert;
|
|||||||
import javafx.scene.control.Alert.AlertType;
|
import javafx.scene.control.Alert.AlertType;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
|
|
||||||
import envoy.client.data.*;
|
|
||||||
import envoy.client.helper.ShutdownHelper;
|
|
||||||
import envoy.client.net.Client;
|
|
||||||
import envoy.client.ui.SceneContext.SceneInfo;
|
|
||||||
import envoy.client.ui.controller.LoginScene;
|
|
||||||
import envoy.client.util.IconUtil;
|
|
||||||
import envoy.data.*;
|
import envoy.data.*;
|
||||||
import envoy.data.User.UserStatus;
|
import envoy.data.User.UserStatus;
|
||||||
import envoy.event.*;
|
import envoy.event.UserStatusChange;
|
||||||
import envoy.exception.EnvoyException;
|
import envoy.exception.EnvoyException;
|
||||||
import envoy.util.EnvoyLog;
|
import envoy.util.EnvoyLog;
|
||||||
|
|
||||||
|
import envoy.client.data.*;
|
||||||
|
import envoy.client.data.shortcuts.EnvoyShortcutConfig;
|
||||||
|
import envoy.client.helper.ShutdownHelper;
|
||||||
|
import envoy.client.net.Client;
|
||||||
|
import envoy.client.ui.controller.LoginScene;
|
||||||
|
import envoy.client.util.IconUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles application startup.
|
* Handles application startup.
|
||||||
*
|
*
|
||||||
@ -36,7 +37,7 @@ public final class Startup extends Application {
|
|||||||
*
|
*
|
||||||
* @since Envoy Client v0.1-beta
|
* @since Envoy Client v0.1-beta
|
||||||
*/
|
*/
|
||||||
public static final String VERSION = "0.1-beta";
|
public static final String VERSION = "0.2-beta";
|
||||||
|
|
||||||
private static LocalDB localDB;
|
private static LocalDB localDB;
|
||||||
|
|
||||||
@ -46,8 +47,8 @@ public final class Startup extends Application {
|
|||||||
private static final Logger logger = EnvoyLog.getLogger(Startup.class);
|
private static final Logger logger = EnvoyLog.getLogger(Startup.class);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads the configuration, initializes the client and the local database and
|
* Loads the configuration, initializes the client and the local database and delegates the rest
|
||||||
* delegates the rest of the startup process to {@link LoginScene}.
|
* of the startup process to {@link LoginScene}.
|
||||||
*
|
*
|
||||||
* @since Envoy Client v0.1-beta
|
* @since Envoy Client v0.1-beta
|
||||||
*/
|
*/
|
||||||
@ -56,7 +57,8 @@ public final class Startup extends Application {
|
|||||||
|
|
||||||
// Initialize config and logger
|
// Initialize config and logger
|
||||||
try {
|
try {
|
||||||
config.loadAll(Startup.class, "client.properties", getParameters().getRaw().toArray(new String[0]));
|
config.loadAll(Startup.class, "client.properties",
|
||||||
|
getParameters().getRaw().toArray(new String[0]));
|
||||||
EnvoyLog.initialize(config);
|
EnvoyLog.initialize(config);
|
||||||
} catch (final IllegalStateException e) {
|
} catch (final IllegalStateException e) {
|
||||||
new Alert(AlertType.ERROR, "Error loading configuration values:\n" + e);
|
new Alert(AlertType.ERROR, "Error loading configuration values:\n" + e);
|
||||||
@ -84,20 +86,25 @@ public final class Startup extends Application {
|
|||||||
stage.setTitle("Envoy");
|
stage.setTitle("Envoy");
|
||||||
stage.getIcons().add(IconUtil.loadIcon("envoy_logo"));
|
stage.getIcons().add(IconUtil.loadIcon("envoy_logo"));
|
||||||
|
|
||||||
|
// Configure global shortcuts
|
||||||
|
EnvoyShortcutConfig.initializeEnvoyShortcuts();
|
||||||
|
|
||||||
// Create scene context
|
// Create scene context
|
||||||
final var sceneContext = new SceneContext(stage);
|
final var sceneContext = new SceneContext(stage);
|
||||||
context.setSceneContext(sceneContext);
|
context.setSceneContext(sceneContext);
|
||||||
|
|
||||||
// Authenticate with token if present
|
// Authenticate with token if present or load login scene
|
||||||
if (localDB.getAuthToken() != null) {
|
if (localDB.getAuthToken() != null) {
|
||||||
logger.info("Attempting authentication with token...");
|
logger.info("Attempting authentication with token...");
|
||||||
localDB.loadUserData();
|
localDB.loadUserData();
|
||||||
if (!performHandshake(
|
if (!performHandshake(
|
||||||
LoginCredentials.loginWithToken(localDB.getUser().getName(), localDB.getAuthToken(), VERSION, localDB.getLastSync())))
|
LoginCredentials.loginWithToken(localDB.getUser().getName(), localDB.getAuthToken(),
|
||||||
|
VERSION, localDB.getLastSync())))
|
||||||
sceneContext.load(SceneInfo.LOGIN_SCENE);
|
sceneContext.load(SceneInfo.LOGIN_SCENE);
|
||||||
} else
|
} else
|
||||||
// Load login scene
|
|
||||||
sceneContext.load(SceneInfo.LOGIN_SCENE);
|
sceneContext.load(SceneInfo.LOGIN_SCENE);
|
||||||
|
|
||||||
|
stage.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -108,18 +115,23 @@ public final class Startup extends Application {
|
|||||||
* @since Envoy Client v0.2-beta
|
* @since Envoy Client v0.2-beta
|
||||||
*/
|
*/
|
||||||
public static boolean performHandshake(LoginCredentials credentials) {
|
public static boolean performHandshake(LoginCredentials credentials) {
|
||||||
final var cacheMap = new CacheMap();
|
final var originalStatus =
|
||||||
cacheMap.put(Message.class, new Cache<Message>());
|
localDB.getUser() == null ? UserStatus.ONLINE : localDB.getUser().getStatus();
|
||||||
cacheMap.put(GroupMessage.class, new Cache<GroupMessage>());
|
|
||||||
cacheMap.put(MessageStatusChange.class, new Cache<MessageStatusChange>());
|
|
||||||
cacheMap.put(GroupMessageStatusChange.class, new Cache<GroupMessageStatusChange>());
|
|
||||||
try {
|
try {
|
||||||
client.performHandshake(credentials, cacheMap);
|
client.performHandshake(credentials);
|
||||||
if (client.isOnline()) {
|
if (client.isOnline()) {
|
||||||
|
|
||||||
|
// Restore the original status as the server automatically returns status ONLINE
|
||||||
|
client.getSender().setStatus(originalStatus);
|
||||||
loadChatScene();
|
loadChatScene();
|
||||||
client.initReceiver(localDB, cacheMap);
|
|
||||||
|
// Request an ID generator if none is present or the existing one is consumed
|
||||||
|
if (!localDB.hasIDGenerator() || !localDB.getIDGenerator().hasNext())
|
||||||
|
client.requestIDGenerator();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} else return false;
|
} else
|
||||||
|
return false;
|
||||||
} catch (IOException | InterruptedException | TimeoutException e) {
|
} catch (IOException | InterruptedException | TimeoutException e) {
|
||||||
logger.log(Level.INFO, "Could not connect to server. Entering offline mode...");
|
logger.log(Level.INFO, "Could not connect to server. Entering offline mode...");
|
||||||
return attemptOfflineMode(credentials.getIdentifier());
|
return attemptOfflineMode(credentials.getIdentifier());
|
||||||
@ -127,8 +139,8 @@ public final class Startup extends Application {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempts to load {@link envoy.client.ui.controller.ChatScene} in offline mode
|
* Attempts to load {@link envoy.client.ui.controller.ChatScene} in offline mode for a given
|
||||||
* for a given user.
|
* user.
|
||||||
*
|
*
|
||||||
* @param identifier the identifier of the user - currently his username
|
* @param identifier the identifier of the user - currently his username
|
||||||
* @return whether the offline mode could be entered
|
* @return whether the offline mode could be entered
|
||||||
@ -138,7 +150,8 @@ public final class Startup extends Application {
|
|||||||
try {
|
try {
|
||||||
// Try entering offline mode
|
// Try entering offline mode
|
||||||
final User clientUser = localDB.getUsers().get(identifier);
|
final User clientUser = localDB.getUsers().get(identifier);
|
||||||
if (clientUser == null) throw new EnvoyException("Could not enter offline mode: user name unknown");
|
if (clientUser == null)
|
||||||
|
throw new EnvoyException("Could not enter offline mode: user name unknown");
|
||||||
client.setSender(clientUser);
|
client.setSender(clientUser);
|
||||||
loadChatScene();
|
loadChatScene();
|
||||||
return true;
|
return true;
|
||||||
@ -170,7 +183,8 @@ public final class Startup extends Application {
|
|||||||
private static void loadChatScene() {
|
private static void loadChatScene() {
|
||||||
|
|
||||||
// Set client user in local database
|
// Set client user in local database
|
||||||
localDB.setUser(client.getSender());
|
final var user = client.getSender();
|
||||||
|
localDB.setUser(user);
|
||||||
|
|
||||||
// Initialize chats in local database
|
// Initialize chats in local database
|
||||||
try {
|
try {
|
||||||
@ -178,14 +192,22 @@ public final class Startup extends Application {
|
|||||||
} catch (final FileNotFoundException e) {
|
} catch (final FileNotFoundException e) {
|
||||||
// The local database file has not yet been created, probably first login
|
// The local database file has not yet been created, probably first login
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
new Alert(AlertType.ERROR, "Error while loading local database: " + e + "\nChats will not be stored locally.").showAndWait();
|
new Alert(AlertType.ERROR,
|
||||||
|
"Error while loading local database: " + e + "\nChats will not be stored locally.")
|
||||||
|
.showAndWait();
|
||||||
logger.log(Level.WARNING, "Could not load local database: ", e);
|
logger.log(Level.WARNING, "Could not load local database: ", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
context.initWriteProxy();
|
context.initWriteProxy();
|
||||||
|
|
||||||
if (client.isOnline()) context.getWriteProxy().flushCache();
|
if (client.isOnline()) {
|
||||||
else
|
context.getWriteProxy().flushCache();
|
||||||
|
|
||||||
|
// Inform the server that this user has a different user status than expected
|
||||||
|
if (!user.getStatus().equals(UserStatus.ONLINE))
|
||||||
|
client.send(new UserStatusChange(user));
|
||||||
|
} else
|
||||||
|
|
||||||
// Set all contacts to offline mode
|
// Set all contacts to offline mode
|
||||||
localDB.getChats()
|
localDB.getChats()
|
||||||
.stream()
|
.stream()
|
||||||
@ -197,26 +219,26 @@ public final class Startup extends Application {
|
|||||||
final var stage = context.getStage();
|
final var stage = context.getStage();
|
||||||
|
|
||||||
// Pop LoginScene if present
|
// Pop LoginScene if present
|
||||||
if (!context.getSceneContext().isEmpty()) context.getSceneContext().pop();
|
if (!context.getSceneContext().isEmpty())
|
||||||
|
context.getSceneContext().pop();
|
||||||
|
|
||||||
// Load ChatScene
|
// Load ChatScene
|
||||||
stage.setMinHeight(400);
|
stage.setMinHeight(400);
|
||||||
stage.setMinWidth(843);
|
stage.setMinWidth(843);
|
||||||
context.getSceneContext().load(SceneContext.SceneInfo.CHAT_SCENE);
|
context.getSceneContext().load(SceneInfo.CHAT_SCENE);
|
||||||
stage.centerOnScreen();
|
stage.centerOnScreen();
|
||||||
|
|
||||||
if (StatusTrayIcon.isSupported()) {
|
|
||||||
|
|
||||||
// Exit or minimize the stage when a close request occurs
|
// Exit or minimize the stage when a close request occurs
|
||||||
stage.setOnCloseRequest(e -> { ShutdownHelper.exit(); if (Settings.getInstance().isHideOnClose()) e.consume(); });
|
stage.setOnCloseRequest(
|
||||||
|
e -> {
|
||||||
|
ShutdownHelper.exit();
|
||||||
|
if (Settings.getInstance().isHideOnClose() && StatusTrayIcon.isSupported())
|
||||||
|
e.consume();
|
||||||
|
});
|
||||||
|
|
||||||
// Initialize status tray icon
|
// Initialize status tray icon
|
||||||
final var trayIcon = new StatusTrayIcon(stage);
|
if (StatusTrayIcon.isSupported())
|
||||||
Settings.getInstance().getItems().get("hideOnClose").setChangeHandler(c -> {
|
new StatusTrayIcon(stage).show();
|
||||||
if ((Boolean) c) trayIcon.show();
|
|
||||||
else trayIcon.hide();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start auto save thread
|
// Start auto save thread
|
||||||
localDB.initAutoSave();
|
localDB.initAutoSave();
|
||||||
|
@ -1,36 +1,62 @@
|
|||||||
package envoy.client.ui;
|
package envoy.client.ui;
|
||||||
|
|
||||||
|
import static java.awt.Image.SCALE_SMOOTH;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.TrayIcon.MessageType;
|
import java.awt.TrayIcon.MessageType;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
|
|
||||||
import envoy.client.helper.ShutdownHelper;
|
import dev.kske.eventbus.core.Event;
|
||||||
import envoy.client.util.IconUtil;
|
import dev.kske.eventbus.core.EventBus;
|
||||||
import envoy.data.Message;
|
|
||||||
|
|
||||||
import dev.kske.eventbus.*;
|
import envoy.data.Message;
|
||||||
import dev.kske.eventbus.Event;
|
import envoy.data.User.UserStatus;
|
||||||
|
|
||||||
|
import envoy.client.data.*;
|
||||||
|
import envoy.client.event.*;
|
||||||
|
import envoy.client.helper.ShutdownHelper;
|
||||||
|
import envoy.client.util.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* A tray icon with the Envoy logo, an "Envoy" tool tip and a pop-up menu with menu items for
|
||||||
|
* <ul>
|
||||||
|
* <li>Changing the user status</li>
|
||||||
|
* <li>Logging out</li>
|
||||||
|
* <li>Quitting Envoy</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
* @author Kai S. K. Engelbart
|
* @author Kai S. K. Engelbart
|
||||||
* @since Envoy Client v0.2-alpha
|
* @since Envoy Client v0.2-alpha
|
||||||
*/
|
*/
|
||||||
public final class StatusTrayIcon implements EventListener {
|
public final class StatusTrayIcon {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link TrayIcon} provided by the System Tray API for controlling the
|
* The {@link TrayIcon} provided by the System Tray API for controlling the system tray. This
|
||||||
* system tray. This includes displaying the icon, but also creating
|
* includes displaying the icon, but also creating notifications when new messages are received.
|
||||||
* notifications when new messages are received.
|
|
||||||
*/
|
*/
|
||||||
private final TrayIcon trayIcon;
|
private final TrayIcon trayIcon;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A received {@link Message} is only displayed as a system tray notification if
|
* A received {@link Message} is only displayed as a system tray notification if this variable
|
||||||
* this variable is set to {@code true}.
|
* is set to {@code true}.
|
||||||
*/
|
*/
|
||||||
private boolean displayMessages;
|
private boolean displayMessageNotification;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The size of the tray icon's image.
|
||||||
|
*/
|
||||||
|
private final Dimension size;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Envoy logo on which the current user status and unread message count will be drawn to
|
||||||
|
* compose the tray icon.
|
||||||
|
*/
|
||||||
|
private final Image logo;
|
||||||
|
|
||||||
|
private static final Font unreadMessageFont = new Font("sans-serif", Font.PLAIN, 8);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return {@code true} if the status tray icon is supported on this platform
|
* @return {@code true} if the status tray icon is supported on this platform
|
||||||
@ -39,31 +65,56 @@ public final class StatusTrayIcon implements EventListener {
|
|||||||
public static boolean isSupported() { return SystemTray.isSupported(); }
|
public static boolean isSupported() { return SystemTray.isSupported(); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@link StatusTrayIcon} with the Envoy logo, a tool tip and a pop-up
|
* Creates a {@link StatusTrayIcon} with the Envoy logo, a tool tip and a pop-up menu.
|
||||||
* menu.
|
|
||||||
*
|
*
|
||||||
* @param stage the stage whose focus determines if message
|
* @param stage the stage whose focus determines if message notifications are displayed
|
||||||
* notifications are displayed
|
|
||||||
* @since Envoy Client v0.2-beta
|
* @since Envoy Client v0.2-beta
|
||||||
*/
|
*/
|
||||||
public StatusTrayIcon(Stage stage) {
|
public StatusTrayIcon(Stage stage) {
|
||||||
trayIcon = new TrayIcon(IconUtil.loadAWTCompatible("/icons/envoy_logo.png"), "Envoy");
|
size = SystemTray.getSystemTray().getTrayIconSize();
|
||||||
trayIcon.setImageAutoSize(true);
|
logo = IconUtil.loadAWTCompatible("/icons/envoy_logo.png").getScaledInstance(size.width,
|
||||||
trayIcon.setToolTip("You are notified if you have unread messages.");
|
size.height, SCALE_SMOOTH);
|
||||||
|
|
||||||
final PopupMenu popup = new PopupMenu();
|
final var popup = new PopupMenu();
|
||||||
|
|
||||||
final MenuItem exitMenuItem = new MenuItem("Exit");
|
// Adding the exit menu item
|
||||||
exitMenuItem.addActionListener(evt -> ShutdownHelper.exit());
|
final var exitMenuItem = new MenuItem("Exit");
|
||||||
|
exitMenuItem.addActionListener(evt -> ShutdownHelper.exit(true));
|
||||||
popup.add(exitMenuItem);
|
popup.add(exitMenuItem);
|
||||||
|
|
||||||
trayIcon.setPopupMenu(popup);
|
// Adding the logout menu item
|
||||||
|
final var logoutMenuItem = new MenuItem("Logout");
|
||||||
|
logoutMenuItem.addActionListener(evt -> Platform.runLater(UserUtil::logout));
|
||||||
|
popup.add(logoutMenuItem);
|
||||||
|
|
||||||
// Only display messages if the stage is not focused
|
// Adding the status change items
|
||||||
stage.focusedProperty().addListener((ov, onHidden, onShown) -> displayMessages = !ov.getValue());
|
final var statusSubMenu = new Menu("Change status");
|
||||||
|
for (final var status : UserStatus.values()) {
|
||||||
|
final var statusMenuItem = new MenuItem(status.toString().toLowerCase());
|
||||||
|
statusMenuItem
|
||||||
|
.addActionListener(evt -> Platform.runLater(() -> UserUtil.changeStatus(status)));
|
||||||
|
statusSubMenu.add(statusMenuItem);
|
||||||
|
}
|
||||||
|
popup.add(statusSubMenu);
|
||||||
|
|
||||||
|
// Initialize the icon
|
||||||
|
trayIcon = new TrayIcon(createImage(), "Envoy", popup);
|
||||||
|
|
||||||
|
// Only display messages if the stage is not focused and the current user status
|
||||||
|
// is not BUSY (if BUSY, displayMessageNotification will be false)
|
||||||
|
stage.focusedProperty()
|
||||||
|
.addListener((ov, wasFocused, isFocused) -> displayMessageNotification =
|
||||||
|
!displayMessageNotification && wasFocused ? false : !isFocused);
|
||||||
|
|
||||||
|
// Listen to changes in the total unread message amount
|
||||||
|
Chat.getTotalUnreadAmount().addListener((ov, oldValue, newValue) -> updateImage());
|
||||||
|
|
||||||
// Show the window if the user clicks on the icon
|
// Show the window if the user clicks on the icon
|
||||||
trayIcon.addActionListener(evt -> Platform.runLater(() -> { stage.setIconified(false); stage.toFront(); stage.requestFocus(); }));
|
trayIcon.addActionListener(evt -> Platform.runLater(() -> {
|
||||||
|
stage.setIconified(false);
|
||||||
|
stage.toFront();
|
||||||
|
stage.requestFocus();
|
||||||
|
}));
|
||||||
|
|
||||||
// Start processing message events
|
// Start processing message events
|
||||||
EventBus.getInstance().registerListener(this);
|
EventBus.getInstance().registerListener(this);
|
||||||
@ -77,7 +128,7 @@ public final class StatusTrayIcon implements EventListener {
|
|||||||
public void show() {
|
public void show() {
|
||||||
try {
|
try {
|
||||||
SystemTray.getSystemTray().add(trayIcon);
|
SystemTray.getSystemTray().add(trayIcon);
|
||||||
} catch (final AWTException e) {}
|
} catch (AWTException e) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -85,13 +136,89 @@ public final class StatusTrayIcon implements EventListener {
|
|||||||
*
|
*
|
||||||
* @since Envoy Client v0.2-beta
|
* @since Envoy Client v0.2-beta
|
||||||
*/
|
*/
|
||||||
public void hide() { SystemTray.getSystemTray().remove(trayIcon); }
|
@Event(Logout.class)
|
||||||
|
public void hide() {
|
||||||
|
SystemTray.getSystemTray().remove(trayIcon);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Event
|
||||||
|
private void onOwnStatusChange(OwnStatusChange statusChange) {
|
||||||
|
displayMessageNotification = !statusChange.get().equals(UserStatus.BUSY);
|
||||||
|
trayIcon.getImage().flush();
|
||||||
|
trayIcon.setImage(createImage());
|
||||||
|
}
|
||||||
|
|
||||||
@Event
|
@Event
|
||||||
private void onMessage(Message message) {
|
private void onMessage(Message message) {
|
||||||
if (displayMessages) trayIcon.displayMessage(
|
if (displayMessageNotification)
|
||||||
message.hasAttachment() ? "New " + message.getAttachment().getType().toString().toLowerCase() + " message received" : "New message received",
|
trayIcon
|
||||||
message.getText(),
|
.displayMessage(message.hasAttachment()
|
||||||
MessageType.INFO);
|
? "New " + message.getAttachment().getType().toString().toLowerCase()
|
||||||
|
+ " message received"
|
||||||
|
: "New message received", message.getText(), MessageType.INFO);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the tray icon's image by first releasing the resources held by the current image and
|
||||||
|
* then setting a new one generated by the {@link StatusTrayIcon#createImage()} method.
|
||||||
|
*
|
||||||
|
* @since Envoy Client v0.3-beta
|
||||||
|
*/
|
||||||
|
private void updateImage() {
|
||||||
|
trayIcon.getImage().flush();
|
||||||
|
trayIcon.setImage(createImage());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Composes an icon that displays the current user status and the amount of unread messages, if
|
||||||
|
* any are present.
|
||||||
|
*
|
||||||
|
* @since Envoy Client v0.3-beta
|
||||||
|
*/
|
||||||
|
private BufferedImage createImage() {
|
||||||
|
|
||||||
|
// Create a new image with the dimensions of the logo
|
||||||
|
var img = new BufferedImage(size.width, size.height, BufferedImage.TYPE_INT_ARGB);
|
||||||
|
|
||||||
|
// Obtain the draw graphics of the image and copy the logo
|
||||||
|
var g = img.createGraphics();
|
||||||
|
g.drawImage(logo, 0, 0, null);
|
||||||
|
|
||||||
|
// Draw the current user status
|
||||||
|
switch (Context.getInstance().getLocalDB().getUser().getStatus()) {
|
||||||
|
case ONLINE:
|
||||||
|
g.setColor(Color.GREEN);
|
||||||
|
break;
|
||||||
|
case AWAY:
|
||||||
|
g.setColor(Color.ORANGE);
|
||||||
|
break;
|
||||||
|
case BUSY:
|
||||||
|
g.setColor(Color.RED);
|
||||||
|
break;
|
||||||
|
case OFFLINE:
|
||||||
|
g.setColor(Color.GRAY);
|
||||||
|
}
|
||||||
|
g.fillOval(size.width / 2, size.height / 2, size.width / 2, size.height / 2);
|
||||||
|
|
||||||
|
// Draw total amount of unread messages, if any are present
|
||||||
|
if (Chat.getTotalUnreadAmount().get() > 0) {
|
||||||
|
|
||||||
|
// Draw black background circle
|
||||||
|
g.setColor(Color.BLACK);
|
||||||
|
g.fillOval(size.width / 2, 0, size.width / 2, size.height / 2);
|
||||||
|
|
||||||
|
// Unread amount in white
|
||||||
|
String unreadAmount = Chat.getTotalUnreadAmount().get() > 9 ? "9+"
|
||||||
|
: String.valueOf(Chat.getTotalUnreadAmount().get());
|
||||||
|
g.setColor(Color.WHITE);
|
||||||
|
g.setFont(unreadMessageFont);
|
||||||
|
g.drawString(unreadAmount,
|
||||||
|
3 * size.width / 4 - g.getFontMetrics().stringWidth(unreadAmount) / 2,
|
||||||
|
size.height / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finish drawing
|
||||||
|
g.dispose();
|
||||||
|
return img;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,194 @@
|
|||||||
|
package envoy.client.ui.chatscene;
|
||||||
|
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.function.*;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
import javafx.scene.control.*;
|
||||||
|
import javafx.scene.control.Alert.AlertType;
|
||||||
|
import javafx.scene.control.skin.VirtualFlow;
|
||||||
|
|
||||||
|
import envoy.data.Message;
|
||||||
|
import envoy.data.User.UserStatus;
|
||||||
|
import envoy.util.EnvoyLog;
|
||||||
|
|
||||||
|
import envoy.client.data.Context;
|
||||||
|
import envoy.client.data.commands.*;
|
||||||
|
import envoy.client.helper.ShutdownHelper;
|
||||||
|
import envoy.client.ui.SceneInfo;
|
||||||
|
import envoy.client.ui.controller.ChatScene;
|
||||||
|
import envoy.client.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains all {@link SystemCommand}s used for {@link envoy.client.ui.controller.ChatScene}.
|
||||||
|
*
|
||||||
|
* @author Leon Hofmeister
|
||||||
|
* @since Envoy Client v0.3-beta
|
||||||
|
*/
|
||||||
|
public final class ChatSceneCommands {
|
||||||
|
|
||||||
|
private final ListView<Message> messageList;
|
||||||
|
private final SystemCommandMap messageTextAreaCommands = new SystemCommandMap();
|
||||||
|
private final SystemCommandBuilder builder =
|
||||||
|
new SystemCommandBuilder(messageTextAreaCommands);
|
||||||
|
|
||||||
|
private static final String messageDependentCommandDescription =
|
||||||
|
" the given message. Use s/S to use the selected message. Otherwise expects a number relative to the uppermost completely visible message.";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param messageList the message list to use for some commands
|
||||||
|
* @param chatScene the instance of {@code ChatScene} that uses this object
|
||||||
|
* @since Envoy Client v0.3-beta
|
||||||
|
*/
|
||||||
|
public ChatSceneCommands(ListView<Message> messageList, ChatScene chatScene) {
|
||||||
|
this.messageList = messageList;
|
||||||
|
|
||||||
|
// Error message initialization
|
||||||
|
builder.setAction(text -> { throw new RuntimeException(); })
|
||||||
|
.setDescription("Shows an error message.").buildNoArg("error");
|
||||||
|
|
||||||
|
// Do A Barrel roll initialization
|
||||||
|
final var random = new Random();
|
||||||
|
builder
|
||||||
|
.setAction(text -> chatScene.doABarrelRoll(Integer.parseInt(text.get(0)),
|
||||||
|
Double.parseDouble(text.get(1))))
|
||||||
|
.setDefaults(Integer.toString(random.nextInt(3) + 1),
|
||||||
|
Double.toString(random.nextDouble() * 3 + 1))
|
||||||
|
.setDescription("See for yourself :)")
|
||||||
|
.setNumberOfArguments(2)
|
||||||
|
.build("dabr");
|
||||||
|
|
||||||
|
// Logout initialization
|
||||||
|
builder.setAction(text -> UserUtil.logout()).setDescription("Logs you out.")
|
||||||
|
.buildNoArg("logout");
|
||||||
|
|
||||||
|
// Exit initialization
|
||||||
|
builder.setAction(text -> ShutdownHelper.exit()).setNumberOfArguments(0)
|
||||||
|
.setDescription("Exits the program.").build("exit", false);
|
||||||
|
builder.build("q");
|
||||||
|
|
||||||
|
// Open settings scene initialization
|
||||||
|
builder
|
||||||
|
.setAction(
|
||||||
|
text -> Context.getInstance().getSceneContext().load(SceneInfo.SETTINGS_SCENE))
|
||||||
|
.setDescription("Opens the settings screen")
|
||||||
|
.buildNoArg("settings");
|
||||||
|
|
||||||
|
// Status change initialization
|
||||||
|
builder.setAction(text -> {
|
||||||
|
try {
|
||||||
|
UserUtil.changeStatus(Enum.valueOf(UserStatus.class, text.get(0).toUpperCase()));
|
||||||
|
} catch (final IllegalArgumentException e) {
|
||||||
|
final var alert = new Alert(AlertType.ERROR);
|
||||||
|
alert.setContentText("Please provide an existing status");
|
||||||
|
alert.showAndWait();
|
||||||
|
}
|
||||||
|
}).setDescription("Changes your status to the given status.").setNumberOfArguments(1)
|
||||||
|
.setDefaults("").build("status");
|
||||||
|
|
||||||
|
// Selection of a new message initialization
|
||||||
|
messageDependantAction("s",
|
||||||
|
m -> {
|
||||||
|
messageList.getSelectionModel().clearSelection();
|
||||||
|
messageList.getSelectionModel().select(m);
|
||||||
|
},
|
||||||
|
m -> true,
|
||||||
|
"Selects");
|
||||||
|
|
||||||
|
// Copy text of selection initialization
|
||||||
|
messageDependantAction("cp", MessageUtil::copyMessageText, m -> !m.getText().isEmpty(),
|
||||||
|
"Copies the text of");
|
||||||
|
|
||||||
|
// Delete selection initialization
|
||||||
|
messageDependantAction("del", MessageUtil::deleteMessage, m -> true, "Deletes");
|
||||||
|
|
||||||
|
// Save attachment of selection initialization
|
||||||
|
messageDependantAction("save-att", MessageUtil::saveAttachment, Message::hasAttachment,
|
||||||
|
"Saves the attachment of");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void messageDependantAction(String command, Consumer<Message> action,
|
||||||
|
Predicate<Message> additionalCheck, String description) {
|
||||||
|
builder.setAction(text -> {
|
||||||
|
final var positionalArgument = text.get(0).toLowerCase();
|
||||||
|
|
||||||
|
// the currently selected message was requested
|
||||||
|
if (positionalArgument.startsWith("s")) {
|
||||||
|
final var relativeString =
|
||||||
|
positionalArgument.length() == 1 ? "" : positionalArgument.substring(1);
|
||||||
|
|
||||||
|
// Only s has been used as input
|
||||||
|
if (positionalArgument.length() == 1) {
|
||||||
|
final var selectedMessage = messageList.getSelectionModel().getSelectedItem();
|
||||||
|
if (selectedMessage != null && additionalCheck.test(selectedMessage))
|
||||||
|
action.accept(selectedMessage);
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Either s++ or s-- has been requested
|
||||||
|
} else if (relativeString.equals("++") || relativeString.equals("--"))
|
||||||
|
selectionNeighbor(action, additionalCheck, positionalArgument);
|
||||||
|
|
||||||
|
// A message relative to the currently selected message should be used (i.e.
|
||||||
|
// s+4)
|
||||||
|
else
|
||||||
|
useRelativeMessage(command, action, additionalCheck, relativeString, true);
|
||||||
|
|
||||||
|
// Either ++s or --s has been requested
|
||||||
|
} else if (positionalArgument.equals("--s") || positionalArgument.equals("++s"))
|
||||||
|
selectionNeighbor(action, additionalCheck, positionalArgument);
|
||||||
|
|
||||||
|
// Just a number is expected: ((+)4)
|
||||||
|
else
|
||||||
|
useRelativeMessage(command, action, additionalCheck, positionalArgument, false);
|
||||||
|
}).setDefaults("s").setNumberOfArguments(1)
|
||||||
|
.setDescription(description.concat(messageDependentCommandDescription)).build(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void selectionNeighbor(Consumer<Message> action, Predicate<Message> additionalCheck,
|
||||||
|
final String positionalArgument) {
|
||||||
|
final var wantedIndex = messageList.getSelectionModel().getSelectedIndex()
|
||||||
|
+ (positionalArgument.contains("+") ? 1 : -1);
|
||||||
|
messageList.getSelectionModel().clearAndSelect(wantedIndex);
|
||||||
|
final var selectedMessage = messageList.getItems().get(wantedIndex);
|
||||||
|
if (selectedMessage != null && additionalCheck.test(selectedMessage))
|
||||||
|
action.accept(selectedMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void useRelativeMessage(String command, Consumer<Message> action,
|
||||||
|
Predicate<Message> additionalCheck, final String positionalArgument,
|
||||||
|
boolean useSelectedMessage) throws NumberFormatException {
|
||||||
|
final var stripPlus =
|
||||||
|
positionalArgument.startsWith("+") ? positionalArgument.substring(1)
|
||||||
|
: positionalArgument;
|
||||||
|
final var incDec = Integer.valueOf(stripPlus);
|
||||||
|
try {
|
||||||
|
|
||||||
|
// The currently selected message is the base message
|
||||||
|
if (useSelectedMessage) {
|
||||||
|
final var messageToUse = messageList.getItems()
|
||||||
|
.get(messageList.getSelectionModel().getSelectedIndex() + incDec);
|
||||||
|
if (messageToUse != null && additionalCheck.test(messageToUse))
|
||||||
|
action.accept(messageToUse);
|
||||||
|
|
||||||
|
// The currently upmost completely visible message is the base message
|
||||||
|
} else {
|
||||||
|
final var messageToUse = messageList.getItems()
|
||||||
|
.get(((VirtualFlow<?>) messageList.lookup(".virtual-flow"))
|
||||||
|
.getFirstVisibleCell().getIndex() + 1 + incDec);
|
||||||
|
if (messageToUse != null && additionalCheck.test(messageToUse))
|
||||||
|
action.accept(messageToUse);
|
||||||
|
}
|
||||||
|
} catch (final IndexOutOfBoundsException e) {
|
||||||
|
EnvoyLog.getLogger(ChatSceneCommands.class)
|
||||||
|
.log(Level.INFO,
|
||||||
|
" A non-existing message was requested by the user for System command "
|
||||||
|
+ command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the map used by this {@code ChatSceneCommands}
|
||||||
|
* @since Envoy Client v0.3-beta
|
||||||
|
*/
|
||||||
|
public SystemCommandMap getChatSceneCommands() { return messageTextAreaCommands; }
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package envoy.client.ui.control;
|
package envoy.client.ui.chatscene;
|
||||||
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
@ -7,8 +7,8 @@ import javafx.scene.control.*;
|
|||||||
import javafx.scene.input.Clipboard;
|
import javafx.scene.input.Clipboard;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays a context menu that offers an additional option when one of
|
* Displays a context menu that offers an additional option when one of its menu items has been
|
||||||
* its menu items has been clicked.
|
* clicked.
|
||||||
* <p>
|
* <p>
|
||||||
* Current options are:
|
* Current options are:
|
||||||
* <ul>
|
* <ul>
|
||||||
@ -24,9 +24,8 @@ import javafx.scene.input.Clipboard;
|
|||||||
*
|
*
|
||||||
* @author Leon Hofmeister
|
* @author Leon Hofmeister
|
||||||
* @since Envoy Client v0.2-beta
|
* @since Envoy Client v0.2-beta
|
||||||
* @apiNote please refrain from using
|
* @apiNote please refrain from using {@link ContextMenu#setOnShowing(EventHandler)} as this is
|
||||||
* {@link ContextMenu#setOnShowing(EventHandler)} as this is already
|
* already used by this component
|
||||||
* used by this component
|
|
||||||
*/
|
*/
|
||||||
public class TextInputContextMenu extends ContextMenu {
|
public class TextInputContextMenu extends ContextMenu {
|
||||||
|
|
||||||
@ -38,11 +37,10 @@ public class TextInputContextMenu extends ContextMenu {
|
|||||||
private final MenuItem deleteMI = new MenuItem("Delete selection");
|
private final MenuItem deleteMI = new MenuItem("Delete selection");
|
||||||
private final MenuItem clearMI = new MenuItem("Clear");
|
private final MenuItem clearMI = new MenuItem("Clear");
|
||||||
private final MenuItem selectAllMI = new MenuItem("Select all");
|
private final MenuItem selectAllMI = new MenuItem("Select all");
|
||||||
private final MenuItem separatorMI = new SeparatorMenuItem();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new {@code TextInputContextMenu} with an optional action when
|
* Creates a new {@code TextInputContextMenu} with an optional action when this menu was
|
||||||
* this menu was clicked. Currently shows:
|
* clicked. Currently shows:
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>undo</li>
|
* <li>undo</li>
|
||||||
* <li>redo</li>
|
* <li>redo</li>
|
||||||
@ -54,14 +52,12 @@ public class TextInputContextMenu extends ContextMenu {
|
|||||||
* <li>Select all</li>
|
* <li>Select all</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* @param control the text input component to display this
|
* @param control the text input component to display this {@code ContextMenu}
|
||||||
* {@code ContextMenu}
|
* @param menuItemClicked the second action to perform when a menu item of this context menu has
|
||||||
* @param menuItemClicked the second action to perform when a menu item of this
|
* been clicked
|
||||||
* context menu has been clicked
|
|
||||||
* @since Envoy Client v0.2-beta
|
* @since Envoy Client v0.2-beta
|
||||||
* @apiNote please refrain from using
|
* @apiNote please refrain from using {@link ContextMenu#setOnShowing(EventHandler)} as this is
|
||||||
* {@link ContextMenu#setOnShowing(EventHandler)} as this is already
|
* already used by this component
|
||||||
* used by this component
|
|
||||||
*/
|
*/
|
||||||
public TextInputContextMenu(TextInputControl control, Consumer<ActionEvent> menuItemClicked) {
|
public TextInputContextMenu(TextInputControl control, Consumer<ActionEvent> menuItemClicked) {
|
||||||
|
|
||||||
@ -82,6 +78,7 @@ public class TextInputContextMenu extends ContextMenu {
|
|||||||
copyMI.disableProperty().bind(control.selectedTextProperty().isEmpty());
|
copyMI.disableProperty().bind(control.selectedTextProperty().isEmpty());
|
||||||
deleteMI.disableProperty().bind(control.selectedTextProperty().isEmpty());
|
deleteMI.disableProperty().bind(control.selectedTextProperty().isEmpty());
|
||||||
clearMI.disableProperty().bind(control.textProperty().isEmpty());
|
clearMI.disableProperty().bind(control.textProperty().isEmpty());
|
||||||
|
selectAllMI.disableProperty().bind(control.textProperty().isEmpty());
|
||||||
setOnShowing(e -> pasteMI.setDisable(!Clipboard.getSystemClipboard().hasString()));
|
setOnShowing(e -> pasteMI.setDisable(!Clipboard.getSystemClipboard().hasString()));
|
||||||
|
|
||||||
selectAllMI.getProperties().put("refreshMenu", Boolean.TRUE);
|
selectAllMI.getProperties().put("refreshMenu", Boolean.TRUE);
|
||||||
@ -89,17 +86,22 @@ public class TextInputContextMenu extends ContextMenu {
|
|||||||
// Add all items to the ContextMenu
|
// Add all items to the ContextMenu
|
||||||
getItems().add(undoMI);
|
getItems().add(undoMI);
|
||||||
getItems().add(redoMI);
|
getItems().add(redoMI);
|
||||||
|
getItems().add(new SeparatorMenuItem());
|
||||||
getItems().add(cutMI);
|
getItems().add(cutMI);
|
||||||
getItems().add(copyMI);
|
getItems().add(copyMI);
|
||||||
getItems().add(pasteMI);
|
getItems().add(pasteMI);
|
||||||
getItems().add(separatorMI);
|
getItems().add(new SeparatorMenuItem());
|
||||||
getItems().add(deleteMI);
|
getItems().add(deleteMI);
|
||||||
getItems().add(clearMI);
|
getItems().add(clearMI);
|
||||||
getItems().add(separatorMI);
|
getItems().add(new SeparatorMenuItem());
|
||||||
getItems().add(selectAllMI);
|
getItems().add(selectAllMI);
|
||||||
}
|
}
|
||||||
|
|
||||||
private EventHandler<ActionEvent> addAction(Consumer<ActionEvent> originalAction, Consumer<ActionEvent> additionalAction) {
|
private EventHandler<ActionEvent> addAction(Consumer<ActionEvent> originalAction,
|
||||||
return e -> { originalAction.accept(e); additionalAction.accept(e); };
|
Consumer<ActionEvent> additionalAction) {
|
||||||
|
return e -> {
|
||||||
|
originalAction.accept(e);
|
||||||
|
additionalAction.accept(e);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* Contains classes that influence the appearance and behavior of ChatScene.
|
||||||
|
*
|
||||||
|
* @author Leon Hofmeister
|
||||||
|
* @since Envoy Client v0.3-beta
|
||||||
|
*/
|
||||||
|
package envoy.client.ui.chatscene;
|
@ -6,10 +6,11 @@ import javafx.scene.control.*;
|
|||||||
import javafx.scene.control.Alert.AlertType;
|
import javafx.scene.control.Alert.AlertType;
|
||||||
import javafx.scene.layout.HBox;
|
import javafx.scene.layout.HBox;
|
||||||
|
|
||||||
import envoy.client.data.audio.AudioPlayer;
|
|
||||||
import envoy.exception.EnvoyException;
|
import envoy.exception.EnvoyException;
|
||||||
import envoy.util.EnvoyLog;
|
import envoy.util.EnvoyLog;
|
||||||
|
|
||||||
|
import envoy.client.data.audio.AudioPlayer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enables the play back of audio clips through a button.
|
* Enables the play back of audio clips through a button.
|
||||||
*
|
*
|
||||||
@ -18,7 +19,7 @@ import envoy.util.EnvoyLog;
|
|||||||
*/
|
*/
|
||||||
public final class AudioControl extends HBox {
|
public final class AudioControl extends HBox {
|
||||||
|
|
||||||
private AudioPlayer player = new AudioPlayer();
|
private final AudioPlayer player = new AudioPlayer();
|
||||||
|
|
||||||
private static final Logger logger = EnvoyLog.getLogger(AudioControl.class);
|
private static final Logger logger = EnvoyLog.getLogger(AudioControl.class);
|
||||||
|
|
||||||
|
@ -2,16 +2,15 @@ package envoy.client.ui.control;
|
|||||||
|
|
||||||
import javafx.geometry.*;
|
import javafx.geometry.*;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.image.*;
|
import javafx.scene.image.Image;
|
||||||
import javafx.scene.layout.*;
|
import javafx.scene.layout.*;
|
||||||
import javafx.scene.shape.Rectangle;
|
|
||||||
|
|
||||||
import envoy.client.data.*;
|
import envoy.client.data.*;
|
||||||
import envoy.client.util.IconUtil;
|
import envoy.client.util.IconUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays a chat using a contact control for the recipient and a label for the
|
* Displays a chat using a contact control for the recipient and a label for the unread message
|
||||||
* unread message count.
|
* count.
|
||||||
*
|
*
|
||||||
* @see ContactControl
|
* @see ContactControl
|
||||||
* @author Leon Hofmeister
|
* @author Leon Hofmeister
|
||||||
@ -19,10 +18,12 @@ import envoy.client.util.IconUtil;
|
|||||||
*/
|
*/
|
||||||
public final class ChatControl extends HBox {
|
public final class ChatControl extends HBox {
|
||||||
|
|
||||||
private static final Image userIcon = IconUtil.loadIconThemeSensitive("user_icon", 32),
|
private static Image userIcon = IconUtil.loadIconThemeSensitive("user_icon", 32),
|
||||||
groupIcon = IconUtil.loadIconThemeSensitive("group_icon", 32);
|
groupIcon = IconUtil.loadIconThemeSensitive("group_icon", 32);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Creates a new {@code ChatControl}.
|
||||||
|
*
|
||||||
* @param chat the chat to display
|
* @param chat the chat to display
|
||||||
* @since Envoy Client v0.1-beta
|
* @since Envoy Client v0.1-beta
|
||||||
*/
|
*/
|
||||||
@ -31,17 +32,12 @@ public final class ChatControl extends HBox {
|
|||||||
setPadding(new Insets(0, 0, 3, 0));
|
setPadding(new Insets(0, 0, 3, 0));
|
||||||
|
|
||||||
// Profile picture
|
// Profile picture
|
||||||
ImageView contactProfilePic = new ImageView(chat instanceof GroupChat ? groupIcon : userIcon);
|
var contactProfilePic =
|
||||||
final var clip = new Rectangle();
|
new ProfilePicImageView(chat instanceof GroupChat ? groupIcon : userIcon, 32);
|
||||||
clip.setWidth(32);
|
|
||||||
clip.setHeight(32);
|
|
||||||
clip.setArcHeight(32);
|
|
||||||
clip.setArcWidth(32);
|
|
||||||
contactProfilePic.setClip(clip);
|
|
||||||
getChildren().add(contactProfilePic);
|
getChildren().add(contactProfilePic);
|
||||||
|
|
||||||
// Spacing
|
// Spacing
|
||||||
final var leftSpacing = new Region();
|
var leftSpacing = new Region();
|
||||||
leftSpacing.setPrefSize(8, 0);
|
leftSpacing.setPrefSize(8, 0);
|
||||||
leftSpacing.setMinSize(8, 0);
|
leftSpacing.setMinSize(8, 0);
|
||||||
leftSpacing.setMaxSize(8, 0);
|
leftSpacing.setMaxSize(8, 0);
|
||||||
@ -52,18 +48,26 @@ public final class ChatControl extends HBox {
|
|||||||
|
|
||||||
// Unread messages
|
// Unread messages
|
||||||
if (chat.getUnreadAmount() != 0) {
|
if (chat.getUnreadAmount() != 0) {
|
||||||
final var spacing = new Region();
|
var spacing = new Region();
|
||||||
setHgrow(spacing, Priority.ALWAYS);
|
setHgrow(spacing, Priority.ALWAYS);
|
||||||
getChildren().add(spacing);
|
getChildren().add(spacing);
|
||||||
final var unreadMessagesLabel = new Label(Integer.toString(chat.getUnreadAmount()));
|
var unreadMessagesLabel = new Label(
|
||||||
|
chat.getUnreadAmount() > 99 ? "99+" : String.valueOf(chat.getUnreadAmount()));
|
||||||
unreadMessagesLabel.setMinSize(15, 15);
|
unreadMessagesLabel.setMinSize(15, 15);
|
||||||
final var vbox = new VBox();
|
unreadMessagesLabel.setAlignment(Pos.CENTER_RIGHT);
|
||||||
vbox.setAlignment(Pos.CENTER_RIGHT);
|
|
||||||
unreadMessagesLabel.setAlignment(Pos.CENTER);
|
|
||||||
unreadMessagesLabel.getStyleClass().add("unread-messages-amount");
|
unreadMessagesLabel.getStyleClass().add("unread-messages-amount");
|
||||||
vbox.getChildren().add(unreadMessagesLabel);
|
getChildren().add(unreadMessagesLabel);
|
||||||
getChildren().add(vbox);
|
|
||||||
}
|
}
|
||||||
getStyleClass().add("list-element");
|
getStyleClass().add("list-element");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reloads the default icons.
|
||||||
|
*
|
||||||
|
* @since Envoy Client v0.3-beta
|
||||||
|
*/
|
||||||
|
public static void reloadDefaultChatIcons() {
|
||||||
|
userIcon = IconUtil.loadIconThemeSensitive("user_icon", 32);
|
||||||
|
groupIcon = IconUtil.loadIconThemeSensitive("group_icon", 32);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,34 +6,46 @@ import javafx.scene.layout.VBox;
|
|||||||
import envoy.data.*;
|
import envoy.data.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays information about a contact in two rows. The first row contains the
|
* Displays information about a contact in two rows. The first row contains the name. The second row
|
||||||
* name. The second row contains the online status (user) or the member count
|
* contains the online status (user) or the member count (group).
|
||||||
* (group).
|
|
||||||
*
|
*
|
||||||
* @author Kai S. K. Engelbart
|
* @author Kai S. K. Engelbart
|
||||||
* @since Envoy Client v0.2-beta
|
* @since Envoy Client v0.2-beta
|
||||||
*/
|
*/
|
||||||
public final class ContactControl extends VBox {
|
public final class ContactControl extends VBox {
|
||||||
|
|
||||||
|
private final Contact contact;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param contact the contact to display
|
* @param contact the contact to display
|
||||||
* @since Envoy Client v0.2-beta
|
* @since Envoy Client v0.2-beta
|
||||||
*/
|
*/
|
||||||
public ContactControl(Contact contact) {
|
public ContactControl(Contact contact) {
|
||||||
|
this.contact = contact;
|
||||||
|
|
||||||
// Name label
|
// Name label
|
||||||
final var nameLabel = new Label(contact.getName());
|
final var nameLabel = new Label(contact.getName());
|
||||||
getChildren().add(nameLabel);
|
getChildren().add(nameLabel);
|
||||||
|
|
||||||
// Online status (user) or member count (group)
|
// Online status (user) or member count (group)
|
||||||
if (contact instanceof User) {
|
getChildren().add(contact instanceof User ? new UserStatusLabel((User) contact)
|
||||||
final var status = ((User) contact).getStatus().toString();
|
: new GroupSizeLabel((Group) contact));
|
||||||
final var statusLabel = new Label(status);
|
|
||||||
statusLabel.getStyleClass().add(status.toLowerCase());
|
|
||||||
getChildren().add(statusLabel);
|
|
||||||
} else {
|
|
||||||
getChildren().add(new Label(contact.getContacts().size() + " members"));
|
|
||||||
}
|
|
||||||
getStyleClass().add("list-element");
|
getStyleClass().add("list-element");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replaces the info label of this {@code ContactControl} with an updated version.
|
||||||
|
* <p>
|
||||||
|
* This method should be called when the status of the underlying user or the size of the
|
||||||
|
* underlying group has changed.
|
||||||
|
*
|
||||||
|
* @since Envoy Client v0.3-beta
|
||||||
|
* @apiNote will produce buggy results if contact control gets updated so that the info label is
|
||||||
|
* no longer on index 1.
|
||||||
|
*/
|
||||||
|
public void replaceInfoLabel() {
|
||||||
|
getChildren().set(1, contact instanceof User ? new UserStatusLabel((User) contact)
|
||||||
|
: new GroupSizeLabel((Group) contact));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,23 @@
|
|||||||
|
package envoy.client.ui.control;
|
||||||
|
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
|
||||||
|
import envoy.data.Group;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays the amount of members in a {@link Group}.
|
||||||
|
*
|
||||||
|
* @author Leon Hofmeister
|
||||||
|
* @since Envoy Client v0.3-beta
|
||||||
|
*/
|
||||||
|
public final class GroupSizeLabel extends Label {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param recipient the group whose members to show
|
||||||
|
* @since Envoy Client v0.3-beta
|
||||||
|
*/
|
||||||
|
public GroupSizeLabel(Group recipient) {
|
||||||
|
super(recipient.getContacts().size() + " member"
|
||||||
|
+ (recipient.getContacts().size() != 1 ? "s" : ""));
|
||||||
|
}
|
||||||
|
}
|
@ -1,8 +1,6 @@
|
|||||||
package envoy.client.ui.control;
|
package envoy.client.ui.control;
|
||||||
|
|
||||||
import java.awt.Toolkit;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.awt.datatransfer.StringSelection;
|
|
||||||
import java.io.*;
|
|
||||||
import java.time.ZoneId;
|
import java.time.ZoneId;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -12,15 +10,15 @@ import javafx.geometry.*;
|
|||||||
import javafx.scene.control.*;
|
import javafx.scene.control.*;
|
||||||
import javafx.scene.image.*;
|
import javafx.scene.image.*;
|
||||||
import javafx.scene.layout.*;
|
import javafx.scene.layout.*;
|
||||||
import javafx.stage.FileChooser;
|
|
||||||
|
|
||||||
import envoy.client.data.*;
|
|
||||||
import envoy.client.ui.*;
|
|
||||||
import envoy.client.util.IconUtil;
|
|
||||||
import envoy.data.*;
|
import envoy.data.*;
|
||||||
import envoy.data.Message.MessageStatus;
|
import envoy.data.Message.MessageStatus;
|
||||||
import envoy.util.EnvoyLog;
|
import envoy.util.EnvoyLog;
|
||||||
|
|
||||||
|
import envoy.client.data.*;
|
||||||
|
import envoy.client.net.Client;
|
||||||
|
import envoy.client.util.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class transforms a single {@link Message} into a UI component.
|
* This class transforms a single {@link Message} into a UI component.
|
||||||
*
|
*
|
||||||
@ -32,21 +30,25 @@ public final class MessageControl extends Label {
|
|||||||
|
|
||||||
private final boolean ownMessage;
|
private final boolean ownMessage;
|
||||||
|
|
||||||
private final LocalDB localDB = Context.getInstance().getLocalDB();
|
private final LocalDB localDB = context.getLocalDB();
|
||||||
private final SceneContext sceneContext = Context.getInstance().getSceneContext();
|
private final Client client = context.getClient();
|
||||||
|
|
||||||
private static final DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm:ss")
|
private static final Context context = Context.getInstance();
|
||||||
|
private static final DateTimeFormatter dateFormat =
|
||||||
|
DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm:ss")
|
||||||
.withZone(ZoneId.systemDefault());
|
.withZone(ZoneId.systemDefault());
|
||||||
private static final Map<MessageStatus, Image> statusImages = IconUtil.loadByEnum(MessageStatus.class, 16);
|
private static final Map<MessageStatus, Image> statusImages =
|
||||||
private static final Settings settings = Settings.getInstance();
|
IconUtil.loadByEnum(MessageStatus.class, 16);
|
||||||
private static final Logger logger = EnvoyLog.getLogger(MessageControl.class);
|
private static final Logger logger =
|
||||||
|
EnvoyLog.getLogger(MessageControl.class);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* @param message the message that should be formatted
|
* @param message the message that should be formatted
|
||||||
* @since Envoy Client v0.1-beta
|
* @since Envoy Client v0.1-beta
|
||||||
*/
|
*/
|
||||||
public MessageControl(Message message) {
|
public MessageControl(Message message) {
|
||||||
|
ownMessage = message.getSenderID() == localDB.getUser().getID();
|
||||||
|
|
||||||
// Creating the underlying VBox and the dateLabel
|
// Creating the underlying VBox and the dateLabel
|
||||||
final var hbox = new HBox();
|
final var hbox = new HBox();
|
||||||
if (message.getSenderID() != localDB.getUser().getID() && message instanceof GroupMessage) {
|
if (message.getSenderID() != localDB.getUser().getID() && message instanceof GroupMessage) {
|
||||||
@ -68,17 +70,39 @@ public final class MessageControl extends Label {
|
|||||||
|
|
||||||
// Creating the actions for the MenuItems
|
// Creating the actions for the MenuItems
|
||||||
final var contextMenu = new ContextMenu();
|
final var contextMenu = new ContextMenu();
|
||||||
final var copyMenuItem = new MenuItem("Copy");
|
final var items = contextMenu.getItems();
|
||||||
final var deleteMenuItem = new MenuItem("Delete");
|
|
||||||
|
// Copy message action
|
||||||
|
if (!message.getText().isEmpty()) {
|
||||||
|
final var copyMenuItem = new MenuItem("Copy Text");
|
||||||
|
copyMenuItem.setOnAction(e -> MessageUtil.copyMessageText(message));
|
||||||
|
items.add(copyMenuItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete message
|
||||||
|
final var deleteMenuItem = new MenuItem("Delete locally");
|
||||||
|
deleteMenuItem.setOnAction(e -> MessageUtil.deleteMessage(message));
|
||||||
|
items.add(deleteMenuItem);
|
||||||
|
|
||||||
|
// As long as these types of messages are not implemented and no caches are
|
||||||
|
// defined for them, we only want them to appear when being online
|
||||||
|
if (client.isOnline()) {
|
||||||
|
|
||||||
|
// Forward menu item
|
||||||
final var forwardMenuItem = new MenuItem("Forward");
|
final var forwardMenuItem = new MenuItem("Forward");
|
||||||
|
forwardMenuItem.setOnAction(e -> MessageUtil.forwardMessage(message));
|
||||||
|
items.add(forwardMenuItem);
|
||||||
|
|
||||||
|
// Quote menu item
|
||||||
final var quoteMenuItem = new MenuItem("Quote");
|
final var quoteMenuItem = new MenuItem("Quote");
|
||||||
|
quoteMenuItem.setOnAction(e -> MessageUtil.quoteMessage(message));
|
||||||
|
items.add(quoteMenuItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info actions
|
||||||
final var infoMenuItem = new MenuItem("Info");
|
final var infoMenuItem = new MenuItem("Info");
|
||||||
copyMenuItem.setOnAction(e -> copyMessage(message));
|
|
||||||
deleteMenuItem.setOnAction(e -> deleteMessage(message));
|
|
||||||
forwardMenuItem.setOnAction(e -> forwardMessage(message));
|
|
||||||
quoteMenuItem.setOnAction(e -> quoteMessage(message));
|
|
||||||
infoMenuItem.setOnAction(e -> loadMessageInfoScene(message));
|
infoMenuItem.setOnAction(e -> loadMessageInfoScene(message));
|
||||||
contextMenu.getItems().addAll(copyMenuItem, deleteMenuItem, forwardMenuItem, quoteMenuItem, infoMenuItem);
|
items.add(infoMenuItem);
|
||||||
|
|
||||||
// Handling message attachment display
|
// Handling message attachment display
|
||||||
// TODO: Add missing attachment types
|
// TODO: Add missing attachment types
|
||||||
@ -86,7 +110,9 @@ public final class MessageControl extends Label {
|
|||||||
switch (message.getAttachment().getType()) {
|
switch (message.getAttachment().getType()) {
|
||||||
case PICTURE:
|
case PICTURE:
|
||||||
vbox.getChildren()
|
vbox.getChildren()
|
||||||
.add(new ImageView(new Image(new ByteArrayInputStream(message.getAttachment().getData()), 256, 256, true, true)));
|
.add(new ImageView(
|
||||||
|
new Image(new ByteArrayInputStream(message.getAttachment().getData()),
|
||||||
|
256, 256, true, true)));
|
||||||
break;
|
break;
|
||||||
case VIDEO:
|
case VIDEO:
|
||||||
break;
|
break;
|
||||||
@ -97,8 +123,8 @@ public final class MessageControl extends Label {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
final var saveAttachment = new MenuItem("Save attachment");
|
final var saveAttachment = new MenuItem("Save attachment");
|
||||||
saveAttachment.setOnAction(e -> saveAttachment(message));
|
saveAttachment.setOnAction(e -> MessageUtil.saveAttachment(message));
|
||||||
contextMenu.getItems().add(saveAttachment);
|
items.add(saveAttachment);
|
||||||
}
|
}
|
||||||
// Creating the textLabel
|
// Creating the textLabel
|
||||||
final var textLabel = new Label(message.getText());
|
final var textLabel = new Label(message.getText());
|
||||||
@ -116,12 +142,9 @@ public final class MessageControl extends Label {
|
|||||||
hBoxBottom.getChildren().add(statusIcon);
|
hBoxBottom.getChildren().add(statusIcon);
|
||||||
hBoxBottom.setAlignment(Pos.BOTTOM_RIGHT);
|
hBoxBottom.setAlignment(Pos.BOTTOM_RIGHT);
|
||||||
getStyleClass().add("own-message");
|
getStyleClass().add("own-message");
|
||||||
ownMessage = true;
|
|
||||||
hbox.setAlignment(Pos.CENTER_RIGHT);
|
hbox.setAlignment(Pos.CENTER_RIGHT);
|
||||||
} else {
|
} else
|
||||||
getStyleClass().add("received-message");
|
getStyleClass().add("received-message");
|
||||||
ownMessage = false;
|
|
||||||
}
|
|
||||||
vbox.getChildren().add(hBoxBottom);
|
vbox.getChildren().add(hBoxBottom);
|
||||||
// Adjusting height and weight of the cell to the corresponding ListView
|
// Adjusting height and weight of the cell to the corresponding ListView
|
||||||
paddingProperty().setValue(new Insets(5, 20, 5, 20));
|
paddingProperty().setValue(new Insets(5, 20, 5, 20));
|
||||||
@ -129,44 +152,13 @@ public final class MessageControl extends Label {
|
|||||||
setGraphic(vbox);
|
setGraphic(vbox);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Context Menu actions
|
private void loadMessageInfoScene(Message message) {
|
||||||
|
logger.log(Level.FINEST, "message info scene was requested for " + message);
|
||||||
private void copyMessage(Message message) {
|
|
||||||
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(message.getText()), null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void deleteMessage(Message message) { logger.log(Level.FINEST, "message deletion was requested for " + message); }
|
|
||||||
|
|
||||||
private void forwardMessage(Message message) { logger.log(Level.FINEST, "message forwarding was requested for " + message); }
|
|
||||||
|
|
||||||
private void quoteMessage(Message message) { logger.log(Level.FINEST, "message quotation was requested for " + message); }
|
|
||||||
|
|
||||||
private void loadMessageInfoScene(Message message) { logger.log(Level.FINEST, "message info scene was requested for " + message); }
|
|
||||||
|
|
||||||
private void saveAttachment(Message message) {
|
|
||||||
File file;
|
|
||||||
final var fileName = message.getAttachment().getName();
|
|
||||||
final var downloadLocation = settings.getDownloadLocation();
|
|
||||||
// Show save file dialog, if the user did not opt-out
|
|
||||||
if (!settings.isDownloadSavedWithoutAsking()) {
|
|
||||||
final var fileChooser = new FileChooser();
|
|
||||||
fileChooser.setInitialFileName(fileName);
|
|
||||||
fileChooser.setInitialDirectory(downloadLocation);
|
|
||||||
file = fileChooser.showSaveDialog(sceneContext.getStage());
|
|
||||||
} else file = new File(downloadLocation, fileName);
|
|
||||||
|
|
||||||
// A file was selected
|
|
||||||
if (file != null) try (FileOutputStream fos = new FileOutputStream(file)) {
|
|
||||||
fos.write(message.getAttachment().getData());
|
|
||||||
logger.log(Level.FINE, "Attachment of message was saved at " + file.getAbsolutePath());
|
|
||||||
} catch (final IOException e) {
|
|
||||||
logger.log(Level.WARNING, "Could not save attachment of " + message + ": ", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return whether the message stored by this {@code MessageControl} has been
|
* @return whether the message stored by this {@code MessageControl} has been sent by this user
|
||||||
* sent by this user of Envoy
|
* of Envoy
|
||||||
* @since Envoy Client v0.1-beta
|
* @since Envoy Client v0.1-beta
|
||||||
*/
|
*/
|
||||||
public boolean isOwnMessage() { return ownMessage; }
|
public boolean isOwnMessage() { return ownMessage; }
|
||||||
|
@ -16,7 +16,9 @@ public final class ProfilePicImageView extends ImageView {
|
|||||||
*
|
*
|
||||||
* @since Envoy Client v0.2-beta
|
* @since Envoy Client v0.2-beta
|
||||||
*/
|
*/
|
||||||
public ProfilePicImageView() { this(null); }
|
public ProfilePicImageView() {
|
||||||
|
this(null);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new {@code ProfilePicImageView}.
|
* Creates a new {@code ProfilePicImageView}.
|
||||||
@ -24,17 +26,20 @@ public final class ProfilePicImageView extends ImageView {
|
|||||||
* @param image the image to display
|
* @param image the image to display
|
||||||
* @since Envoy Client v0.2-beta
|
* @since Envoy Client v0.2-beta
|
||||||
*/
|
*/
|
||||||
public ProfilePicImageView(Image image) { this(image, 40); }
|
public ProfilePicImageView(Image image) {
|
||||||
|
this(image, 40);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new {@code ProfilePicImageView}.
|
* Creates a new {@code ProfilePicImageView}.
|
||||||
*
|
*
|
||||||
* @param image the image to display
|
* @param image the image to display
|
||||||
* @param sizeAndRounding the size and rounding for a circular
|
* @param sizeAndRounding the size and rounding for a circular {@code ProfilePicImageView}
|
||||||
* {@code ProfilePicImageView}
|
|
||||||
* @since Envoy Client v0.2-beta
|
* @since Envoy Client v0.2-beta
|
||||||
*/
|
*/
|
||||||
public ProfilePicImageView(Image image, double sizeAndRounding) { this(image, sizeAndRounding, sizeAndRounding); }
|
public ProfilePicImageView(Image image, double sizeAndRounding) {
|
||||||
|
this(image, sizeAndRounding, sizeAndRounding);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new {@code ProfilePicImageView}.
|
* Creates a new {@code ProfilePicImageView}.
|
||||||
|
@ -0,0 +1,94 @@
|
|||||||
|
package envoy.client.ui.control;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import javafx.geometry.*;
|
||||||
|
import javafx.scene.control.*;
|
||||||
|
import javafx.scene.image.ImageView;
|
||||||
|
import javafx.scene.layout.*;
|
||||||
|
import javafx.scene.shape.Rectangle;
|
||||||
|
|
||||||
|
import envoy.data.User;
|
||||||
|
|
||||||
|
import envoy.client.util.IconUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays an {@link User} as a quick select control which is used in the quick select list.
|
||||||
|
*
|
||||||
|
* @author Maximilian Käfer
|
||||||
|
* @since Envoy Client v0.3-beta
|
||||||
|
*/
|
||||||
|
public class QuickSelectControl extends VBox {
|
||||||
|
|
||||||
|
private final User user;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance of the {@code QuickSelectControl}.
|
||||||
|
*
|
||||||
|
* @param user the contact whose data is used to create this instance.
|
||||||
|
* @param action the action to perform when a contact is removed with this control as a
|
||||||
|
* parameter
|
||||||
|
* @since Envoy Client v0.3-beta
|
||||||
|
*/
|
||||||
|
public QuickSelectControl(User user, Consumer<QuickSelectControl> action) {
|
||||||
|
this.user = user;
|
||||||
|
setPadding(new Insets(1, 0, 0, 0));
|
||||||
|
setPrefWidth(37);
|
||||||
|
setMaxWidth(37);
|
||||||
|
setMinWidth(37);
|
||||||
|
var stackPane = new StackPane();
|
||||||
|
stackPane.setAlignment(Pos.TOP_CENTER);
|
||||||
|
|
||||||
|
// Profile picture
|
||||||
|
var picHold = new VBox();
|
||||||
|
picHold.setPadding(new Insets(2, 0, 0, 0));
|
||||||
|
picHold.setPrefHeight(35);
|
||||||
|
picHold.setMaxHeight(35);
|
||||||
|
picHold.setMinHeight(35);
|
||||||
|
var contactProfilePic =
|
||||||
|
new ImageView(IconUtil.loadIconThemeSensitive("user_icon", 32));
|
||||||
|
final var clip = new Rectangle();
|
||||||
|
clip.setWidth(32);
|
||||||
|
clip.setHeight(32);
|
||||||
|
clip.setArcHeight(32);
|
||||||
|
clip.setArcWidth(32);
|
||||||
|
contactProfilePic.setClip(clip);
|
||||||
|
picHold.getChildren().add(contactProfilePic);
|
||||||
|
stackPane.getChildren().add(picHold);
|
||||||
|
|
||||||
|
var hBox = new HBox();
|
||||||
|
hBox.setPrefHeight(12);
|
||||||
|
hBox.setMaxHeight(12);
|
||||||
|
hBox.setMinHeight(12);
|
||||||
|
var region = new Region();
|
||||||
|
hBox.getChildren().add(region);
|
||||||
|
HBox.setHgrow(region, Priority.ALWAYS);
|
||||||
|
|
||||||
|
var removeBtn = new Button();
|
||||||
|
removeBtn.setPrefSize(12, 12);
|
||||||
|
removeBtn.setMaxSize(12, 12);
|
||||||
|
removeBtn.setMinSize(12, 12);
|
||||||
|
removeBtn.setOnMouseClicked(evt -> action.accept(this));
|
||||||
|
removeBtn.setId("remove-button");
|
||||||
|
hBox.getChildren().add(removeBtn);
|
||||||
|
stackPane.getChildren().add(hBox);
|
||||||
|
getChildren().add(stackPane);
|
||||||
|
|
||||||
|
var nameLabel = new Label();
|
||||||
|
nameLabel.setPrefSize(35, 20);
|
||||||
|
nameLabel.setMaxSize(35, 20);
|
||||||
|
nameLabel.setMinSize(35, 20);
|
||||||
|
nameLabel.setText(user.getName());
|
||||||
|
nameLabel.setAlignment(Pos.TOP_CENTER);
|
||||||
|
nameLabel.setPadding(new Insets(0, 5, 0, 0));
|
||||||
|
getChildren().add(nameLabel);
|
||||||
|
|
||||||
|
getStyleClass().add("quick-select");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the user whose data is used in this instance
|
||||||
|
* @since Envoy Client v0.3-beta
|
||||||
|
*/
|
||||||
|
public User getUser() { return user; }
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
package envoy.client.ui.control;
|
||||||
|
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
|
||||||
|
import envoy.data.User;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays the status of a {@link User}.
|
||||||
|
*
|
||||||
|
* @author Leon Hofmeister
|
||||||
|
* @since Envoy Client v0.3-beta
|
||||||
|
*/
|
||||||
|
public final class UserStatusLabel extends Label {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param user the user whose status to display
|
||||||
|
* @since Envoy Client v0.3-beta
|
||||||
|
*/
|
||||||
|
public UserStatusLabel(User user) {
|
||||||
|
super(user.getStatus().toString());
|
||||||
|
getStyleClass().add(user.getStatus().toString().toLowerCase());
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +1,14 @@
|
|||||||
package envoy.client.ui.controller;
|
package envoy.client.ui.controller;
|
||||||
|
|
||||||
|
import static envoy.client.ui.SceneInfo.SETTINGS_SCENE;
|
||||||
|
|
||||||
import java.awt.Toolkit;
|
import java.awt.Toolkit;
|
||||||
import java.awt.datatransfer.StringSelection;
|
import java.awt.datatransfer.StringSelection;
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.Random;
|
import java.util.Map;
|
||||||
import java.util.logging.*;
|
import java.util.logging.*;
|
||||||
|
|
||||||
import javafx.animation.RotateTransition;
|
import javafx.animation.RotateTransition;
|
||||||
@ -14,37 +16,38 @@ import javafx.application.Platform;
|
|||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
import javafx.collections.transformation.FilteredList;
|
import javafx.collections.transformation.FilteredList;
|
||||||
import javafx.fxml.*;
|
import javafx.fxml.*;
|
||||||
|
import javafx.geometry.Pos;
|
||||||
import javafx.scene.control.*;
|
import javafx.scene.control.*;
|
||||||
import javafx.scene.control.Alert.AlertType;
|
import javafx.scene.control.Alert.AlertType;
|
||||||
import javafx.scene.image.*;
|
import javafx.scene.image.*;
|
||||||
import javafx.scene.input.*;
|
import javafx.scene.input.*;
|
||||||
import javafx.scene.layout.*;
|
import javafx.scene.layout.HBox;
|
||||||
import javafx.scene.paint.Color;
|
import javafx.scene.paint.Color;
|
||||||
import javafx.scene.shape.Rectangle;
|
import javafx.scene.shape.Rectangle;
|
||||||
import javafx.stage.FileChooser;
|
import javafx.stage.FileChooser;
|
||||||
import javafx.util.Duration;
|
import javafx.util.Duration;
|
||||||
|
|
||||||
import envoy.client.data.*;
|
import dev.kske.eventbus.core.*;
|
||||||
import envoy.client.data.audio.AudioRecorder;
|
import dev.kske.eventbus.core.Event;
|
||||||
import envoy.client.data.commands.*;
|
|
||||||
import envoy.client.event.*;
|
|
||||||
import envoy.client.helper.ShutdownHelper;
|
|
||||||
import envoy.client.net.*;
|
|
||||||
import envoy.client.ui.*;
|
|
||||||
import envoy.client.ui.SceneContext.SceneInfo;
|
|
||||||
import envoy.client.ui.control.*;
|
|
||||||
import envoy.client.ui.listcell.*;
|
|
||||||
import envoy.client.util.*;
|
|
||||||
import envoy.data.*;
|
import envoy.data.*;
|
||||||
import envoy.data.Attachment.AttachmentType;
|
import envoy.data.Attachment.AttachmentType;
|
||||||
import envoy.data.Message.MessageStatus;
|
import envoy.data.Message.MessageStatus;
|
||||||
import envoy.event.*;
|
import envoy.event.*;
|
||||||
import envoy.event.contact.ContactOperation;
|
import envoy.event.contact.UserOperation;
|
||||||
import envoy.exception.EnvoyException;
|
import envoy.exception.EnvoyException;
|
||||||
import envoy.util.EnvoyLog;
|
import envoy.util.EnvoyLog;
|
||||||
|
|
||||||
import dev.kske.eventbus.*;
|
import envoy.client.data.*;
|
||||||
import dev.kske.eventbus.Event;
|
import envoy.client.data.audio.AudioRecorder;
|
||||||
|
import envoy.client.data.shortcuts.KeyboardMapping;
|
||||||
|
import envoy.client.event.*;
|
||||||
|
import envoy.client.net.*;
|
||||||
|
import envoy.client.ui.*;
|
||||||
|
import envoy.client.ui.chatscene.*;
|
||||||
|
import envoy.client.ui.control.*;
|
||||||
|
import envoy.client.ui.listcell.*;
|
||||||
|
import envoy.client.util.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Controller for the chat scene.
|
* Controller for the chat scene.
|
||||||
@ -52,13 +55,7 @@ import dev.kske.eventbus.Event;
|
|||||||
* @author Kai S. K. Engelbart
|
* @author Kai S. K. Engelbart
|
||||||
* @since Envoy Client v0.1-beta
|
* @since Envoy Client v0.1-beta
|
||||||
*/
|
*/
|
||||||
public final class ChatScene implements EventListener, Restorable {
|
public final class ChatScene implements Restorable, KeyboardMapping {
|
||||||
|
|
||||||
@FXML
|
|
||||||
private GridPane scene;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private Label contactLabel;
|
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private ListView<Message> messageList;
|
private ListView<Message> messageList;
|
||||||
@ -87,27 +84,21 @@ public final class ChatScene implements EventListener, Restorable {
|
|||||||
@FXML
|
@FXML
|
||||||
private Button newContactButton;
|
private Button newContactButton;
|
||||||
|
|
||||||
@FXML
|
|
||||||
private TextArea messageTextArea;
|
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private Label remainingChars;
|
private Label remainingChars;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private Label infoLabel;
|
private Label infoLabel;
|
||||||
|
|
||||||
@FXML
|
|
||||||
private MenuItem deleteContactMenuItem;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private ImageView attachmentView;
|
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private Label topBarContactLabel;
|
private Label topBarContactLabel;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private Label topBarStatusLabel;
|
private Label topBarStatusLabel;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private ImageView attachmentView;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private ImageView clientProfilePic;
|
private ImageView clientProfilePic;
|
||||||
|
|
||||||
@ -115,10 +106,10 @@ public final class ChatScene implements EventListener, Restorable {
|
|||||||
private ImageView recipientProfilePic;
|
private ImageView recipientProfilePic;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private TextArea contactSearch;
|
private TextArea messageTextArea;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private VBox contactOperations;
|
private TextArea contactSearch;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private TabPane tabPane;
|
private TabPane tabPane;
|
||||||
@ -132,22 +123,26 @@ public final class ChatScene implements EventListener, Restorable {
|
|||||||
@FXML
|
@FXML
|
||||||
private HBox contactSpecificOnlineOperations;
|
private HBox contactSpecificOnlineOperations;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private HBox ownContactControl;
|
||||||
|
|
||||||
private Chat currentChat;
|
private Chat currentChat;
|
||||||
private FilteredList<Chat> chats;
|
private FilteredList<Chat> chats;
|
||||||
private boolean recording;
|
|
||||||
private Attachment pendingAttachment;
|
private Attachment pendingAttachment;
|
||||||
private boolean postingPermanentlyDisabled;
|
private boolean postingPermanentlyDisabled;
|
||||||
private boolean isCustomAttachmentImage;
|
private boolean isCustomAttachmentImage;
|
||||||
|
private ChatSceneCommands commands;
|
||||||
|
|
||||||
private final LocalDB localDB = context.getLocalDB();
|
private final LocalDB localDB = context.getLocalDB();
|
||||||
private final Client client = context.getClient();
|
private final Client client = context.getClient();
|
||||||
private final WriteProxy writeProxy = context.getWriteProxy();
|
private final WriteProxy writeProxy = context.getWriteProxy();
|
||||||
private final SceneContext sceneContext = context.getSceneContext();
|
private final SceneContext sceneContext = context.getSceneContext();
|
||||||
private final AudioRecorder recorder = new AudioRecorder();
|
private final AudioRecorder recorder = new AudioRecorder();
|
||||||
private final SystemCommandMap messageTextAreaCommands = new SystemCommandMap();
|
private final Tooltip onlyIfOnlineTooltip =
|
||||||
private final Tooltip onlyIfOnlineTooltip = new Tooltip("You need to be online to do this");
|
new Tooltip("You need to be online to do this");
|
||||||
|
|
||||||
private static Image DEFAULT_ATTACHMENT_VIEW_IMAGE = IconUtil.loadIconThemeSensitive("attachment_present", 20);
|
private static Image DEFAULT_ATTACHMENT_VIEW_IMAGE =
|
||||||
|
IconUtil.loadIconThemeSensitive("attachment_present", 20);
|
||||||
|
|
||||||
private static final Settings settings = Settings.getInstance();
|
private static final Settings settings = Settings.getInstance();
|
||||||
private static final EventBus eventBus = EventBus.getInstance();
|
private static final EventBus eventBus = EventBus.getInstance();
|
||||||
@ -164,22 +159,28 @@ public final class ChatScene implements EventListener, Restorable {
|
|||||||
@FXML
|
@FXML
|
||||||
private void initialize() {
|
private void initialize() {
|
||||||
eventBus.registerListener(this);
|
eventBus.registerListener(this);
|
||||||
|
commands = new ChatSceneCommands(messageList, this);
|
||||||
|
|
||||||
// Initialize message and user rendering
|
// Initialize message and user rendering
|
||||||
messageList.setCellFactory(MessageListCell::new);
|
messageList.setCellFactory(MessageListCell::new);
|
||||||
chatList.setCellFactory(new ListCellFactory<>(ChatControl::new));
|
chatList.setCellFactory(ChatListCell::new);
|
||||||
|
|
||||||
// JavaFX provides an internal way of populating the context menu of a text
|
// JavaFX provides an internal way of populating the context menu of a text
|
||||||
// area.
|
// area.
|
||||||
// We, however, need additional functionality.
|
// We, however, need additional functionality.
|
||||||
messageTextArea.setContextMenu(new TextInputContextMenu(messageTextArea, e -> checkKeyCombination(null)));
|
messageTextArea.setContextMenu(
|
||||||
|
new TextInputContextMenu(messageTextArea, e -> checkKeyCombination(null)));
|
||||||
|
|
||||||
// Set the icons of buttons and image views
|
// Set the icons of buttons and image views
|
||||||
settingsButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("settings", DEFAULT_ICON_SIZE)));
|
settingsButton.setGraphic(
|
||||||
voiceButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("microphone", DEFAULT_ICON_SIZE)));
|
new ImageView(IconUtil.loadIconThemeSensitive("settings", 22)));
|
||||||
attachmentButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("attachment", DEFAULT_ICON_SIZE)));
|
voiceButton.setGraphic(
|
||||||
|
new ImageView(IconUtil.loadIconThemeSensitive("microphone", DEFAULT_ICON_SIZE)));
|
||||||
|
attachmentButton.setGraphic(
|
||||||
|
new ImageView(IconUtil.loadIconThemeSensitive("attachment", DEFAULT_ICON_SIZE)));
|
||||||
attachmentView.setImage(DEFAULT_ATTACHMENT_VIEW_IMAGE);
|
attachmentView.setImage(DEFAULT_ATTACHMENT_VIEW_IMAGE);
|
||||||
messageSearchButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("search", DEFAULT_ICON_SIZE)));
|
messageSearchButton.setGraphic(
|
||||||
|
new ImageView(IconUtil.loadIconThemeSensitive("search", DEFAULT_ICON_SIZE)));
|
||||||
clientProfilePic.setImage(IconUtil.loadIconThemeSensitive("user_icon", 43));
|
clientProfilePic.setImage(IconUtil.loadIconThemeSensitive("user_icon", 43));
|
||||||
onlyIfOnlineTooltip.setShowDelay(Duration.millis(250));
|
onlyIfOnlineTooltip.setShowDelay(Duration.millis(250));
|
||||||
final var clip = new Rectangle();
|
final var clip = new Rectangle();
|
||||||
@ -190,20 +191,25 @@ public final class ChatScene implements EventListener, Restorable {
|
|||||||
clientProfilePic.setClip(clip);
|
clientProfilePic.setClip(clip);
|
||||||
|
|
||||||
chatList.setItems(chats = new FilteredList<>(localDB.getChats()));
|
chatList.setItems(chats = new FilteredList<>(localDB.getChats()));
|
||||||
contactLabel.setText(localDB.getUser().getName());
|
|
||||||
|
|
||||||
initializeSystemCommandsMap();
|
// Set the design of the box in the upper-left corner
|
||||||
|
generateOwnStatusControl();
|
||||||
|
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
final var online = client.isOnline();
|
final var online = client.isOnline();
|
||||||
|
|
||||||
// no check will be performed in case it has already been disabled - a negative
|
// no check will be performed in case it has already been disabled - a negative
|
||||||
// GroupCreationResult might have been returned
|
// GroupCreationResult might have been returned
|
||||||
if (!newGroupButton.isDisabled()) newGroupButton.setDisable(!online);
|
if (!newGroupButton.isDisabled())
|
||||||
|
newGroupButton.setDisable(!online);
|
||||||
newContactButton.setDisable(!online);
|
newContactButton.setDisable(!online);
|
||||||
if (online) try {
|
if (online)
|
||||||
|
try {
|
||||||
Tooltip.uninstall(contactSpecificOnlineOperations, onlyIfOnlineTooltip);
|
Tooltip.uninstall(contactSpecificOnlineOperations, onlyIfOnlineTooltip);
|
||||||
contactSearchTab.setContent(new FXMLLoader().load(getClass().getResourceAsStream("/fxml/ContactSearchTab.fxml")));
|
contactSearchTab.setContent(new FXMLLoader()
|
||||||
groupCreationTab.setContent(new FXMLLoader().load(getClass().getResourceAsStream("/fxml/GroupCreationTab.fxml")));
|
.load(getClass().getResourceAsStream("/fxml/ContactSearchTab.fxml")));
|
||||||
|
groupCreationTab.setContent(new FXMLLoader()
|
||||||
|
.load(getClass().getResourceAsStream("/fxml/GroupCreationTab.fxml")));
|
||||||
} catch (final IOException e) {
|
} catch (final IOException e) {
|
||||||
logger.log(Level.SEVERE, "An error occurred when attempting to load tabs: ", e);
|
logger.log(Level.SEVERE, "An error occurred when attempting to load tabs: ", e);
|
||||||
}
|
}
|
||||||
@ -214,33 +220,40 @@ public final class ChatScene implements EventListener, Restorable {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Event(eventType = BackEvent.class)
|
@Event(BackEvent.class)
|
||||||
private void onBackEvent() { tabPane.getSelectionModel().select(Tabs.CONTACT_LIST.ordinal()); }
|
private void onBackEvent() {
|
||||||
|
tabPane.getSelectionModel().select(Tabs.CONTACT_LIST.ordinal());
|
||||||
|
}
|
||||||
|
|
||||||
@Event(includeSubtypes = true)
|
@Event
|
||||||
|
@Polymorphic
|
||||||
private void onMessage(Message message) {
|
private void onMessage(Message message) {
|
||||||
|
|
||||||
// The sender of the message is the recipient of the chat
|
// The sender of the message is the recipient of the chat
|
||||||
// Exceptions: this user is the sender (sync) or group message (group is
|
// Exceptions: this user is the sender (sync) or group message (group is
|
||||||
// recipient)
|
// recipient)
|
||||||
final boolean ownMessage = message.getSenderID() == localDB.getUser().getID();
|
final var ownMessage = message.getSenderID() == localDB.getUser().getID();
|
||||||
final var recipientID = message instanceof GroupMessage || ownMessage ? message.getRecipientID() : message.getSenderID();
|
final var recipientID =
|
||||||
|
message instanceof GroupMessage || ownMessage ? message.getRecipientID()
|
||||||
|
: message.getSenderID();
|
||||||
|
|
||||||
localDB.getChat(recipientID).ifPresent(chat -> {
|
localDB.getChat(recipientID).ifPresent(chat -> {
|
||||||
|
Platform.runLater(() -> {
|
||||||
chat.insert(message);
|
chat.insert(message);
|
||||||
|
|
||||||
// Read current chat or increment unread amount
|
// Read current chat or increment unread amount
|
||||||
if (chat.equals(currentChat)) {
|
if (chat.equals(currentChat)) {
|
||||||
currentChat.read(writeProxy);
|
currentChat.read(writeProxy);
|
||||||
Platform.runLater(this::scrollToMessageListEnd);
|
scrollToMessageListEnd();
|
||||||
} else if (!ownMessage && message.getStatus() != MessageStatus.READ) chat.incrementUnreadAmount();
|
} else if (!ownMessage && message.getStatus() != MessageStatus.READ)
|
||||||
|
chat.incrementUnreadAmount();
|
||||||
|
|
||||||
// Move chat with most recent unread messages to the top
|
// Move chat with most recent unread messages to the top
|
||||||
Platform.runLater(() -> {
|
|
||||||
chats.getSource().remove(chat);
|
chats.getSource().remove(chat);
|
||||||
((ObservableList<Chat>) chats.getSource()).add(0, chat);
|
((ObservableList<Chat>) chats.getSource()).add(0, chat);
|
||||||
|
|
||||||
if (chat.equals(currentChat)) chatList.getSelectionModel().select(0);
|
if (chat.equals(currentChat))
|
||||||
|
chatList.getSelectionModel().select(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -250,30 +263,49 @@ public final class ChatScene implements EventListener, Restorable {
|
|||||||
|
|
||||||
// Update UI if in current chat and the current user was the sender of the
|
// Update UI if in current chat and the current user was the sender of the
|
||||||
// message
|
// message
|
||||||
if (currentChat != null) localDB.getMessage(evt.getID())
|
if (currentChat != null)
|
||||||
|
localDB.getMessage(evt.getID())
|
||||||
.filter(msg -> msg.getSenderID() == client.getSender().getID())
|
.filter(msg -> msg.getSenderID() == client.getSender().getID())
|
||||||
.ifPresent(msg -> Platform.runLater(messageList::refresh));
|
.ifPresent(msg -> Platform.runLater(messageList::refresh));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Event(eventType = UserStatusChange.class)
|
@Event
|
||||||
private void onUserStatusChange() { Platform.runLater(chatList::refresh); }
|
private void onUserStatusChange(UserStatusChange statusChange) {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
chatList.refresh();
|
||||||
|
|
||||||
|
// Replacing the display in the top bar
|
||||||
|
if (currentChat != null && currentChat.getRecipient().getID() == statusChange.getID()) {
|
||||||
|
topBarStatusLabel.getStyleClass().clear();
|
||||||
|
topBarStatusLabel.setText(statusChange.get().toString());
|
||||||
|
topBarStatusLabel.getStyleClass().add(statusChange.get().toString().toLowerCase());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Event
|
@Event
|
||||||
private void onContactOperation(ContactOperation operation) {
|
private void onUserOperation(UserOperation operation) {
|
||||||
final var contact = operation.get();
|
|
||||||
switch (operation.getOperationType()) {
|
// All ADD dependent logic resides in LocalDB
|
||||||
case ADD:
|
if (operation.getOperationType().equals(ElementOperation.REMOVE))
|
||||||
if (contact instanceof User) localDB.getUsers().put(contact.getName(), (User) contact);
|
Platform.runLater(() -> disableChat(new ContactDisabled(operation.get())));
|
||||||
final var chat = contact instanceof User ? new Chat(contact) : new GroupChat(client.getSender(), contact);
|
|
||||||
Platform.runLater(() -> ((ObservableList<Chat>) chats.getSource()).add(0, chat));
|
|
||||||
break;
|
|
||||||
case REMOVE:
|
|
||||||
Platform.runLater(() -> chats.getSource().removeIf(c -> c.getRecipient().equals(contact)));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Event(eventType = NoAttachments.class)
|
@Event
|
||||||
|
private void onGroupResize(GroupResize resize) {
|
||||||
|
final var chatFound = localDB.getChat(resize.getGroupID());
|
||||||
|
chatFound.ifPresent(chat -> Platform.runLater(() -> {
|
||||||
|
chatList.refresh();
|
||||||
|
|
||||||
|
// Update the top-bar status label if all conditions apply
|
||||||
|
if (currentChat != null && currentChat.getRecipient().equals(chat.getRecipient()))
|
||||||
|
topBarStatusLabel
|
||||||
|
.setText(chat.getRecipient().getContacts().size() + " member"
|
||||||
|
+ (currentChat.getRecipient().getContacts().size() != 1 ? "s" : ""));
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Event(NoAttachments.class)
|
||||||
private void onNoAttachments() {
|
private void onNoAttachments() {
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
attachmentButton.setDisable(true);
|
attachmentButton.setDisable(true);
|
||||||
@ -287,59 +319,50 @@ public final class ChatScene implements EventListener, Restorable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Event
|
@Event
|
||||||
private void onGroupCreationResult(GroupCreationResult result) { Platform.runLater(() -> newGroupButton.setDisable(!result.get())); }
|
@Priority(150)
|
||||||
|
private void onGroupCreationResult(GroupCreationResult result) {
|
||||||
|
Platform.runLater(() -> newGroupButton.setDisable(result.get() == null));
|
||||||
|
}
|
||||||
|
|
||||||
@Event(eventType = ThemeChangeEvent.class)
|
@Event(ThemeChangeEvent.class)
|
||||||
private void onThemeChange() {
|
private void onThemeChange() {
|
||||||
settingsButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("settings", DEFAULT_ICON_SIZE)));
|
ChatControl.reloadDefaultChatIcons();
|
||||||
voiceButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("microphone", DEFAULT_ICON_SIZE)));
|
settingsButton.setGraphic(
|
||||||
attachmentButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("attachment", DEFAULT_ICON_SIZE)));
|
new ImageView(IconUtil.loadIconThemeSensitive("settings", DEFAULT_ICON_SIZE)));
|
||||||
|
voiceButton.setGraphic(
|
||||||
|
new ImageView(IconUtil.loadIconThemeSensitive("microphone", DEFAULT_ICON_SIZE)));
|
||||||
|
attachmentButton.setGraphic(
|
||||||
|
new ImageView(IconUtil.loadIconThemeSensitive("attachment", DEFAULT_ICON_SIZE)));
|
||||||
DEFAULT_ATTACHMENT_VIEW_IMAGE = IconUtil.loadIconThemeSensitive("attachment_present", 20);
|
DEFAULT_ATTACHMENT_VIEW_IMAGE = IconUtil.loadIconThemeSensitive("attachment_present", 20);
|
||||||
attachmentView.setImage(isCustomAttachmentImage ? attachmentView.getImage() : DEFAULT_ATTACHMENT_VIEW_IMAGE);
|
attachmentView.setImage(
|
||||||
messageSearchButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("search", DEFAULT_ICON_SIZE)));
|
isCustomAttachmentImage ? attachmentView.getImage() : DEFAULT_ATTACHMENT_VIEW_IMAGE);
|
||||||
|
messageSearchButton.setGraphic(
|
||||||
|
new ImageView(IconUtil.loadIconThemeSensitive("search", DEFAULT_ICON_SIZE)));
|
||||||
clientProfilePic.setImage(IconUtil.loadIconThemeSensitive("user_icon", 43));
|
clientProfilePic.setImage(IconUtil.loadIconThemeSensitive("user_icon", 43));
|
||||||
chatList.setCellFactory(new ListCellFactory<>(ChatControl::new));
|
chatList.setCellFactory(new ListCellFactory<>(ChatControl::new));
|
||||||
messageList.setCellFactory(MessageListCell::new);
|
messageList.setCellFactory(MessageListCell::new);
|
||||||
// TODO: cache image
|
if (currentChat != null)
|
||||||
if (currentChat.getRecipient() instanceof User) recipientProfilePic.setImage(IconUtil.loadIconThemeSensitive("user_icon", 43));
|
if (currentChat.getRecipient() instanceof User)
|
||||||
else recipientProfilePic.setImage(IconUtil.loadIconThemeSensitive("group_icon", 43));
|
recipientProfilePic.setImage(IconUtil.loadIconThemeSensitive("user_icon", 43));
|
||||||
|
else
|
||||||
|
recipientProfilePic.setImage(IconUtil.loadIconThemeSensitive("group_icon", 43));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Event(eventType = Logout.class, priority = 200)
|
@Event(Logout.class)
|
||||||
private void onLogout() { eventBus.removeListener(this); }
|
@Priority(200)
|
||||||
|
private void onLogout() {
|
||||||
|
eventBus.removeListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
@Event(AccountDeletion.class)
|
||||||
* Initializes all {@code SystemCommands} used in {@code ChatScene}.
|
private void onAccountDeletion() {
|
||||||
*
|
Platform.runLater(chatList::refresh);
|
||||||
* @since Envoy Client v0.2-beta
|
|
||||||
*/
|
|
||||||
private void initializeSystemCommandsMap() {
|
|
||||||
final var builder = new SystemCommandBuilder(messageTextAreaCommands);
|
|
||||||
|
|
||||||
// Do A Barrel roll initialization
|
|
||||||
final var random = new Random();
|
|
||||||
builder.setAction(text -> doABarrelRoll(Integer.parseInt(text.get(0)), Double.parseDouble(text.get(1))))
|
|
||||||
.setDefaults(Integer.toString(random.nextInt(3) + 1), Double.toString(random.nextDouble() * 3 + 1))
|
|
||||||
.setDescription("See for yourself :)")
|
|
||||||
.setNumberOfArguments(2)
|
|
||||||
.build("dabr");
|
|
||||||
|
|
||||||
// Logout initialization
|
|
||||||
builder.setAction(text -> ShutdownHelper.logout()).setNumberOfArguments(0).setDescription("Logs you out.").build("logout");
|
|
||||||
|
|
||||||
// Exit initialization
|
|
||||||
builder.setAction(text -> ShutdownHelper.exit()).setNumberOfArguments(0).setDescription("Exits the program").build("exit", false);
|
|
||||||
builder.build("q");
|
|
||||||
|
|
||||||
// Open settings scene initialization
|
|
||||||
builder.setAction(text -> sceneContext.load(SceneInfo.SETTINGS_SCENE))
|
|
||||||
.setNumberOfArguments(0)
|
|
||||||
.setDescription("Opens the settings screen")
|
|
||||||
.build("settings");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRestore() { updateRemainingCharsLabel(); }
|
public void onRestore() {
|
||||||
|
updateRemainingCharsLabel();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Actions to perform when the list of contacts has been clicked.
|
* Actions to perform when the list of contacts has been clicked.
|
||||||
@ -348,9 +371,13 @@ public final class ChatScene implements EventListener, Restorable {
|
|||||||
*/
|
*/
|
||||||
@FXML
|
@FXML
|
||||||
private void chatListClicked() {
|
private void chatListClicked() {
|
||||||
if (chatList.getSelectionModel().isEmpty()) return;
|
if (chatList.getSelectionModel().isEmpty())
|
||||||
|
return;
|
||||||
|
final var chat = chatList.getSelectionModel().getSelectedItem();
|
||||||
|
if (chat == null)
|
||||||
|
return;
|
||||||
|
|
||||||
final var user = chatList.getSelectionModel().getSelectedItem().getRecipient();
|
final var user = chat.getRecipient();
|
||||||
if (user != null && (currentChat == null || !user.equals(currentChat.getRecipient()))) {
|
if (user != null && (currentChat == null || !user.equals(currentChat.getRecipient()))) {
|
||||||
|
|
||||||
// LEON: JFC <===> JAVA FRIED CHICKEN <=/=> Java Foundation Classes
|
// LEON: JFC <===> JAVA FRIED CHICKEN <=/=> Java Foundation Classes
|
||||||
@ -362,7 +389,6 @@ public final class ChatScene implements EventListener, Restorable {
|
|||||||
final var scrollIndex = messageList.getItems().size() - currentChat.getUnreadAmount();
|
final var scrollIndex = messageList.getItems().size() - currentChat.getUnreadAmount();
|
||||||
messageList.scrollTo(scrollIndex);
|
messageList.scrollTo(scrollIndex);
|
||||||
logger.log(Level.FINEST, "Loading chat with " + user + " at index " + scrollIndex);
|
logger.log(Level.FINEST, "Loading chat with " + user + " at index " + scrollIndex);
|
||||||
deleteContactMenuItem.setText("Delete " + user.getName());
|
|
||||||
|
|
||||||
// Read the current chat
|
// Read the current chat
|
||||||
currentChat.read(writeProxy);
|
currentChat.read(writeProxy);
|
||||||
@ -370,38 +396,50 @@ public final class ChatScene implements EventListener, Restorable {
|
|||||||
// Discard the pending attachment
|
// Discard the pending attachment
|
||||||
if (recorder.isRecording()) {
|
if (recorder.isRecording()) {
|
||||||
recorder.cancel();
|
recorder.cancel();
|
||||||
recording = false;
|
voiceButton.setGraphic(new ImageView(
|
||||||
|
IconUtil.loadIconThemeSensitive("microphone", DEFAULT_ICON_SIZE)));
|
||||||
|
voiceButton.setText(null);
|
||||||
}
|
}
|
||||||
pendingAttachment = null;
|
pendingAttachment = null;
|
||||||
updateAttachmentView(false);
|
updateAttachmentView(false);
|
||||||
|
|
||||||
remainingChars.setVisible(true);
|
remainingChars.setVisible(true);
|
||||||
remainingChars
|
remainingChars
|
||||||
.setText(String.format("remaining chars: %d/%d", MAX_MESSAGE_LENGTH - messageTextArea.getText().length(), MAX_MESSAGE_LENGTH));
|
.setText(String.format("remaining chars: %d/%d",
|
||||||
|
MAX_MESSAGE_LENGTH - messageTextArea.getText().length(), MAX_MESSAGE_LENGTH));
|
||||||
}
|
}
|
||||||
messageTextArea.setDisable(currentChat == null || postingPermanentlyDisabled);
|
|
||||||
voiceButton.setDisable(!recorder.isSupported());
|
// Enable or disable the necessary UI controls
|
||||||
attachmentButton.setDisable(false);
|
final var chatEditable = currentChat == null || currentChat.isDisabled();
|
||||||
|
messageTextArea.setDisable(chatEditable || postingPermanentlyDisabled);
|
||||||
|
voiceButton.setDisable(!recorder.isSupported() || chatEditable);
|
||||||
|
attachmentButton.setDisable(chatEditable);
|
||||||
chatList.refresh();
|
chatList.refresh();
|
||||||
|
|
||||||
|
// Design the top bar
|
||||||
if (currentChat != null) {
|
if (currentChat != null) {
|
||||||
topBarContactLabel.setText(currentChat.getRecipient().getName());
|
topBarContactLabel.setText(currentChat.getRecipient().getName());
|
||||||
|
topBarContactLabel.setVisible(true);
|
||||||
|
topBarStatusLabel.setVisible(true);
|
||||||
if (currentChat.getRecipient() instanceof User) {
|
if (currentChat.getRecipient() instanceof User) {
|
||||||
final String status = ((User) currentChat.getRecipient()).getStatus().toString();
|
final var status = ((User) currentChat.getRecipient()).getStatus().toString();
|
||||||
topBarStatusLabel.setText(status);
|
topBarStatusLabel.setText(status);
|
||||||
|
topBarStatusLabel.getStyleClass().clear();
|
||||||
topBarStatusLabel.getStyleClass().add(status.toLowerCase());
|
topBarStatusLabel.getStyleClass().add(status.toLowerCase());
|
||||||
recipientProfilePic.setImage(IconUtil.loadIconThemeSensitive("user_icon", 43));
|
recipientProfilePic.setImage(IconUtil.loadIconThemeSensitive("user_icon", 43));
|
||||||
} else {
|
} else {
|
||||||
topBarStatusLabel.setText(currentChat.getRecipient().getContacts().size() + " members");
|
topBarStatusLabel
|
||||||
|
.setText(currentChat.getRecipient().getContacts().size() + " member"
|
||||||
|
+ (currentChat.getRecipient().getContacts().size() != 1 ? "s" : ""));
|
||||||
|
topBarStatusLabel.getStyleClass().clear();
|
||||||
recipientProfilePic.setImage(IconUtil.loadIconThemeSensitive("group_icon", 43));
|
recipientProfilePic.setImage(IconUtil.loadIconThemeSensitive("group_icon", 43));
|
||||||
}
|
}
|
||||||
final Rectangle clip = new Rectangle();
|
final var clip = new Rectangle();
|
||||||
clip.setWidth(43);
|
clip.setWidth(43);
|
||||||
clip.setHeight(43);
|
clip.setHeight(43);
|
||||||
clip.setArcHeight(43);
|
clip.setArcHeight(43);
|
||||||
clip.setArcWidth(43);
|
clip.setArcWidth(43);
|
||||||
recipientProfilePic.setClip(clip);
|
recipientProfilePic.setClip(clip);
|
||||||
|
|
||||||
messageSearchButton.setVisible(true);
|
messageSearchButton.setVisible(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -412,7 +450,9 @@ public final class ChatScene implements EventListener, Restorable {
|
|||||||
* @since Envoy Client v0.1-beta
|
* @since Envoy Client v0.1-beta
|
||||||
*/
|
*/
|
||||||
@FXML
|
@FXML
|
||||||
private void settingsButtonClicked() { sceneContext.load(SceneContext.SceneInfo.SETTINGS_SCENE); }
|
private void settingsButtonClicked() {
|
||||||
|
sceneContext.load(SETTINGS_SCENE);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Actions to perform when the "Add Contact" - Button has been clicked.
|
* Actions to perform when the "Add Contact" - Button has been clicked.
|
||||||
@ -420,29 +460,35 @@ public final class ChatScene implements EventListener, Restorable {
|
|||||||
* @since Envoy Client v0.1-beta
|
* @since Envoy Client v0.1-beta
|
||||||
*/
|
*/
|
||||||
@FXML
|
@FXML
|
||||||
private void addContactButtonClicked() { tabPane.getSelectionModel().select(Tabs.CONTACT_SEARCH.ordinal()); }
|
private void addContactButtonClicked() {
|
||||||
|
tabPane.getSelectionModel().select(Tabs.CONTACT_SEARCH.ordinal());
|
||||||
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private void groupCreationButtonClicked() { tabPane.getSelectionModel().select(Tabs.GROUP_CREATION.ordinal()); }
|
private void groupCreationButtonClicked() {
|
||||||
|
tabPane.getSelectionModel().select(Tabs.GROUP_CREATION.ordinal());
|
||||||
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private void voiceButtonClicked() {
|
private void voiceButtonClicked() {
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
try {
|
try {
|
||||||
if (!recording) {
|
if (!recorder.isRecording()) {
|
||||||
recording = true;
|
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
voiceButton.setText("Recording");
|
voiceButton.setText("Recording");
|
||||||
voiceButton.setGraphic(new ImageView(IconUtil.loadIcon("microphone_recording", DEFAULT_ICON_SIZE)));
|
voiceButton.setGraphic(new ImageView(
|
||||||
|
IconUtil.loadIcon("microphone_recording", DEFAULT_ICON_SIZE)));
|
||||||
});
|
});
|
||||||
recorder.start();
|
recorder.start();
|
||||||
} else {
|
} else {
|
||||||
pendingAttachment = new Attachment(recorder.finish(), "Voice_recording_"
|
pendingAttachment = new Attachment(recorder.finish(), "Voice_recording_"
|
||||||
+ DateTimeFormatter.ofPattern("yyyy_MM_dd-HH_mm_ss").format(LocalDateTime.now()) + "." + AudioRecorder.FILE_FORMAT,
|
+ DateTimeFormatter.ofPattern("yyyy_MM_dd-HH_mm_ss")
|
||||||
|
.format(LocalDateTime.now())
|
||||||
|
+ "." + AudioRecorder.FILE_FORMAT,
|
||||||
AttachmentType.VOICE);
|
AttachmentType.VOICE);
|
||||||
recording = false;
|
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
voiceButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("microphone", DEFAULT_ICON_SIZE)));
|
voiceButton.setGraphic(new ImageView(
|
||||||
|
IconUtil.loadIconThemeSensitive("microphone", DEFAULT_ICON_SIZE)));
|
||||||
voiceButton.setText(null);
|
voiceButton.setText(null);
|
||||||
checkPostConditions(false);
|
checkPostConditions(false);
|
||||||
updateAttachmentView(true);
|
updateAttachmentView(true);
|
||||||
@ -450,7 +496,8 @@ public final class ChatScene implements EventListener, Restorable {
|
|||||||
}
|
}
|
||||||
} catch (final EnvoyException e) {
|
} catch (final EnvoyException e) {
|
||||||
logger.log(Level.SEVERE, "Could not record audio: ", e);
|
logger.log(Level.SEVERE, "Could not record audio: ", e);
|
||||||
Platform.runLater(new Alert(AlertType.ERROR, "Could not record audio")::showAndWait);
|
Platform
|
||||||
|
.runLater(new Alert(AlertType.ERROR, "Could not record audio")::showAndWait);
|
||||||
}
|
}
|
||||||
}).start();
|
}).start();
|
||||||
}
|
}
|
||||||
@ -472,7 +519,8 @@ public final class ChatScene implements EventListener, Restorable {
|
|||||||
|
|
||||||
// Check max file size
|
// Check max file size
|
||||||
if (file.length() > 16E6) {
|
if (file.length() > 16E6) {
|
||||||
new Alert(AlertType.WARNING, "The selected file exceeds the size limit of 16MB!").showAndWait();
|
new Alert(AlertType.WARNING, "The selected file exceeds the size limit of 16MB!")
|
||||||
|
.showAndWait();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -494,7 +542,8 @@ public final class ChatScene implements EventListener, Restorable {
|
|||||||
checkPostConditions(false);
|
checkPostConditions(false);
|
||||||
// Setting the preview image as image of the attachmentView
|
// Setting the preview image as image of the attachmentView
|
||||||
if (type == AttachmentType.PICTURE) {
|
if (type == AttachmentType.PICTURE) {
|
||||||
attachmentView.setImage(new Image(new ByteArrayInputStream(fileBytes), DEFAULT_ICON_SIZE, DEFAULT_ICON_SIZE, true, true));
|
attachmentView.setImage(new Image(new ByteArrayInputStream(fileBytes),
|
||||||
|
DEFAULT_ICON_SIZE, DEFAULT_ICON_SIZE, true, true));
|
||||||
isCustomAttachmentImage = true;
|
isCustomAttachmentImage = true;
|
||||||
}
|
}
|
||||||
attachmentView.setVisible(true);
|
attachmentView.setVisible(true);
|
||||||
@ -505,14 +554,13 @@ public final class ChatScene implements EventListener, Restorable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rotates every element in our application by {@code rotations}*360° in
|
* Rotates every element in our application by {@code rotations}*360° in {@code an}.
|
||||||
* {@code an}.
|
|
||||||
*
|
*
|
||||||
* @param rotations the amount of times the scene is rotated by 360°
|
* @param rotations the amount of times the scene is rotated by 360°
|
||||||
* @param animationTime the time in seconds that this animation lasts
|
* @param animationTime the time in seconds that this animation lasts
|
||||||
* @since Envoy Client v0.1-beta
|
* @since Envoy Client v0.1-beta
|
||||||
*/
|
*/
|
||||||
private void doABarrelRoll(int rotations, double animationTime) {
|
public void doABarrelRoll(int rotations, double animationTime) {
|
||||||
// Limiting the rotations and duration
|
// Limiting the rotations and duration
|
||||||
rotations = Math.min(rotations, 100000);
|
rotations = Math.min(rotations, 100000);
|
||||||
rotations = Math.max(rotations, 1);
|
rotations = Math.max(rotations, 1);
|
||||||
@ -523,7 +571,8 @@ public final class ChatScene implements EventListener, Restorable {
|
|||||||
final var rotatableNodes = ReflectionUtil.getAllDeclaredNodeVariables(this);
|
final var rotatableNodes = ReflectionUtil.getAllDeclaredNodeVariables(this);
|
||||||
for (final var node : rotatableNodes) {
|
for (final var node : rotatableNodes) {
|
||||||
// Sets the animation duration to {animationTime}
|
// Sets the animation duration to {animationTime}
|
||||||
final var rotateTransition = new RotateTransition(Duration.seconds(animationTime), node);
|
final var rotateTransition =
|
||||||
|
new RotateTransition(Duration.seconds(animationTime), node);
|
||||||
// rotates every element {rotations} times
|
// rotates every element {rotations} times
|
||||||
rotateTransition.setByAngle(rotations * 360);
|
rotateTransition.setByAngle(rotations * 360);
|
||||||
rotateTransition.play();
|
rotateTransition.play();
|
||||||
@ -534,9 +583,8 @@ public final class ChatScene implements EventListener, Restorable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks the text length of the {@code messageTextArea}, adjusts the
|
* Checks the text length of the {@code messageTextArea}, adjusts the {@code remainingChars}
|
||||||
* {@code remainingChars} label and checks whether to send the message
|
* label and checks whether to send the message automatically.
|
||||||
* automatically.
|
|
||||||
*
|
*
|
||||||
* @param e the key event that will be analyzed for a post request
|
* @param e the key event that will be analyzed for a post request
|
||||||
* @since Envoy Client v0.1-beta
|
* @since Envoy Client v0.1-beta
|
||||||
@ -549,29 +597,20 @@ public final class ChatScene implements EventListener, Restorable {
|
|||||||
|
|
||||||
// Sending an IsTyping event if none has been sent for
|
// Sending an IsTyping event if none has been sent for
|
||||||
// IsTyping#millisecondsActive
|
// IsTyping#millisecondsActive
|
||||||
if (client.isOnline() && currentChat.getLastWritingEvent() + IsTyping.millisecondsActive <= System.currentTimeMillis()) {
|
if (client.isOnline() && currentChat.getLastWritingEvent()
|
||||||
client.send(new IsTyping(getChatID(), currentChat.getRecipient().getID()));
|
+ IsTyping.millisecondsActive <= System.currentTimeMillis()) {
|
||||||
|
client.send(new IsTyping(currentChat.getRecipient().getID()));
|
||||||
currentChat.lastWritingEventWasNow();
|
currentChat.lastWritingEventWasNow();
|
||||||
}
|
}
|
||||||
|
|
||||||
// KeyPressed will be called before the char has been added to the text, hence
|
// KeyPressed will be called before the char has been added to the text, hence
|
||||||
// this is needed for the first char
|
// this is needed for the first char
|
||||||
if (messageTextArea.getText().length() == 1 && e != null) checkPostConditions(e);
|
if (messageTextArea.getText().length() == 1 && e != null)
|
||||||
|
checkPostConditions(e);
|
||||||
|
|
||||||
// This is needed for the messageTA context menu
|
// This is needed for the messageTA context menu
|
||||||
else if (e == null) checkPostConditions(false);
|
else if (e == null)
|
||||||
}
|
checkPostConditions(false);
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the id that should be used to send things to the server: the id of
|
|
||||||
* 'our' {@link User} if the recipient of that object is another User, else the
|
|
||||||
* id of the {@link Group} 'our' user is sending to.
|
|
||||||
*
|
|
||||||
* @return an id that can be sent to the server
|
|
||||||
* @since Envoy Client v0.2-beta
|
|
||||||
*/
|
|
||||||
private long getChatID() {
|
|
||||||
return currentChat.getRecipient() instanceof User ? client.getSender().getID() : currentChat.getRecipient().getID();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -581,21 +620,25 @@ public final class ChatScene implements EventListener, Restorable {
|
|||||||
@FXML
|
@FXML
|
||||||
private void checkPostConditions(KeyEvent e) {
|
private void checkPostConditions(KeyEvent e) {
|
||||||
final var enterPressed = e.getCode() == KeyCode.ENTER;
|
final var enterPressed = e.getCode() == KeyCode.ENTER;
|
||||||
final var messagePosted = enterPressed ? settings.isEnterToSend() ? !e.isControlDown() : e.isControlDown() : false;
|
final var messagePosted =
|
||||||
|
enterPressed ? settings.isEnterToSend() ? !e.isControlDown() : e.isControlDown()
|
||||||
|
: false;
|
||||||
if (messagePosted) {
|
if (messagePosted) {
|
||||||
|
|
||||||
// Removing an inserted line break if added by pressing enter
|
// Removing an inserted line break if added by pressing enter
|
||||||
final var text = messageTextArea.getText();
|
final var text = messageTextArea.getText();
|
||||||
final var textPosition = messageTextArea.getCaretPosition() - 1;
|
final var textPosition = messageTextArea.getCaretPosition() - 1;
|
||||||
if (!e.isControlDown() && !text.isEmpty() && text.charAt(textPosition) == '\n')
|
if (!e.isControlDown() && !text.isEmpty() && text.charAt(textPosition) == '\n')
|
||||||
messageTextArea.setText(new StringBuilder(text).deleteCharAt(textPosition).toString());
|
messageTextArea
|
||||||
|
.setText(new StringBuilder(text).deleteCharAt(textPosition).toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
// if control is pressed, the enter press is originally invalidated. Here it'll
|
// if control is pressed, the enter press is originally invalidated. Here it'll
|
||||||
// be inserted again
|
// be inserted again
|
||||||
else if (enterPressed && e.isControlDown()) {
|
else if (enterPressed && e.isControlDown()) {
|
||||||
var caretPosition = messageTextArea.getCaretPosition();
|
var caretPosition = messageTextArea.getCaretPosition();
|
||||||
messageTextArea.setText(new StringBuilder(messageTextArea.getText()).insert(caretPosition, '\n').toString());
|
messageTextArea.setText(new StringBuilder(messageTextArea.getText())
|
||||||
|
.insert(caretPosition, '\n').toString());
|
||||||
messageTextArea.positionCaret(++caretPosition);
|
messageTextArea.positionCaret(++caretPosition);
|
||||||
}
|
}
|
||||||
checkPostConditions(messagePosted);
|
checkPostConditions(messagePosted);
|
||||||
@ -603,8 +646,10 @@ public final class ChatScene implements EventListener, Restorable {
|
|||||||
|
|
||||||
private void checkPostConditions(boolean postMessage) {
|
private void checkPostConditions(boolean postMessage) {
|
||||||
if (!postingPermanentlyDisabled) {
|
if (!postingPermanentlyDisabled) {
|
||||||
if (!postButton.isDisabled() && postMessage) postMessage();
|
if (!postButton.isDisabled() && postMessage)
|
||||||
postButton.setDisable(messageTextArea.getText().isBlank() && pendingAttachment == null || currentChat == null);
|
postMessage();
|
||||||
|
postButton.setDisable(messageTextArea.getText().isBlank() && pendingAttachment == null
|
||||||
|
|| currentChat == null);
|
||||||
} else {
|
} else {
|
||||||
final var noMoreMessaging = "Go online to send messages";
|
final var noMoreMessaging = "Go online to send messages";
|
||||||
if (!infoLabel.getText().equals(noMoreMessaging))
|
if (!infoLabel.getText().equals(noMoreMessaging))
|
||||||
@ -638,13 +683,14 @@ public final class ChatScene implements EventListener, Restorable {
|
|||||||
private void updateRemainingCharsLabel() {
|
private void updateRemainingCharsLabel() {
|
||||||
final var currentLength = messageTextArea.getText().length();
|
final var currentLength = messageTextArea.getText().length();
|
||||||
final var remainingLength = MAX_MESSAGE_LENGTH - currentLength;
|
final var remainingLength = MAX_MESSAGE_LENGTH - currentLength;
|
||||||
remainingChars.setText(String.format("remaining chars: %d/%d", remainingLength, MAX_MESSAGE_LENGTH));
|
remainingChars
|
||||||
|
.setText(String.format("remaining chars: %d/%d", remainingLength, MAX_MESSAGE_LENGTH));
|
||||||
remainingChars.setTextFill(Color.rgb(currentLength, remainingLength, 0, 1));
|
remainingChars.setTextFill(Color.rgb(currentLength, remainingLength, 0, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends a new {@link Message} or {@link GroupMessage} to the server based on
|
* Sends a new {@link Message} or {@link GroupMessage} to the server based on the text entered
|
||||||
* the text entered in the {@code messageTextArea} and the given attachment.
|
* in the {@code messageTextArea} and the given attachment.
|
||||||
*
|
*
|
||||||
* @since Envoy Client v0.1-beta
|
* @since Envoy Client v0.1-beta
|
||||||
*/
|
*/
|
||||||
@ -659,9 +705,10 @@ public final class ChatScene implements EventListener, Restorable {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final var text = messageTextArea.getText().strip();
|
final var text = messageTextArea.getText().strip();
|
||||||
if (!messageTextAreaCommands.executeIfAnyPresent(text)) {
|
if (!commands.getChatSceneCommands().executeIfPresent(text)) {
|
||||||
// Creating the message and its metadata
|
// Creating the message and its metadata
|
||||||
final var builder = new MessageBuilder(localDB.getUser().getID(), currentChat.getRecipient().getID(), localDB.getIDGenerator())
|
final var builder = new MessageBuilder(localDB.getUser().getID(),
|
||||||
|
currentChat.getRecipient().getID(), localDB.getIDGenerator())
|
||||||
.setText(text);
|
.setText(text);
|
||||||
// Setting an attachment, if present
|
// Setting an attachment, if present
|
||||||
if (pendingAttachment != null) {
|
if (pendingAttachment != null) {
|
||||||
@ -670,7 +717,8 @@ public final class ChatScene implements EventListener, Restorable {
|
|||||||
updateAttachmentView(false);
|
updateAttachmentView(false);
|
||||||
}
|
}
|
||||||
// Building the final message
|
// Building the final message
|
||||||
final var message = currentChat.getRecipient() instanceof Group ? builder.buildGroupMessage((Group) currentChat.getRecipient())
|
final var message = currentChat.getRecipient() instanceof Group
|
||||||
|
? builder.buildGroupMessage((Group) currentChat.getRecipient())
|
||||||
: builder.build();
|
: builder.build();
|
||||||
|
|
||||||
// Send message
|
// Send message
|
||||||
@ -682,14 +730,15 @@ public final class ChatScene implements EventListener, Restorable {
|
|||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
chats.getSource().remove(currentChat);
|
chats.getSource().remove(currentChat);
|
||||||
((ObservableList<Chat>) chats.getSource()).add(0, currentChat);
|
((ObservableList<Chat>) chats.getSource()).add(0, currentChat);
|
||||||
chatList.getSelectionModel().select(0);
|
|
||||||
localDB.getChats().remove(currentChat);
|
localDB.getChats().remove(currentChat);
|
||||||
localDB.getChats().add(0, currentChat);
|
localDB.getChats().add(0, currentChat);
|
||||||
|
chatList.getSelectionModel().select(0);
|
||||||
});
|
});
|
||||||
scrollToMessageListEnd();
|
scrollToMessageListEnd();
|
||||||
|
|
||||||
// Request a new ID generator if all IDs were used
|
// Request a new ID generator if all IDs were used
|
||||||
if (!localDB.getIDGenerator().hasNext() && client.isOnline()) client.requestIDGenerator();
|
if (!localDB.getIDGenerator().hasNext() && client.isOnline())
|
||||||
|
client.requestIDGenerator();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear text field and disable post button
|
// Clear text field and disable post button
|
||||||
@ -704,14 +753,16 @@ public final class ChatScene implements EventListener, Restorable {
|
|||||||
*
|
*
|
||||||
* @since Envoy Client v0.1-beta
|
* @since Envoy Client v0.1-beta
|
||||||
*/
|
*/
|
||||||
private void scrollToMessageListEnd() { messageList.scrollTo(messageList.getItems().size() - 1); }
|
private void scrollToMessageListEnd() {
|
||||||
|
messageList.scrollTo(messageList.getItems().size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the {@code infoLabel}.
|
* Updates the {@code infoLabel}.
|
||||||
*
|
*
|
||||||
* @param text the text to use
|
* @param text the text to use
|
||||||
* @param infoLabelID the id the the {@code infoLabel} should have so that it
|
* @param infoLabelID the id the the {@code infoLabel} should have so that it can be styled
|
||||||
* can be styled accordingly in CSS
|
* accordingly in CSS
|
||||||
* @since Envoy Client v0.1-beta
|
* @since Envoy Client v0.1-beta
|
||||||
*/
|
*/
|
||||||
private void updateInfoLabel(String text, String infoLabelID) {
|
private void updateInfoLabel(String text, String infoLabelID) {
|
||||||
@ -722,26 +773,91 @@ public final class ChatScene implements EventListener, Restorable {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the {@code attachmentView} in terms of visibility.<br>
|
* Updates the {@code attachmentView} in terms of visibility.<br>
|
||||||
* Additionally resets the shown image to {@code DEFAULT_ATTACHMENT_VIEW_IMAGE}
|
* Additionally resets the shown image to {@code DEFAULT_ATTACHMENT_VIEW_IMAGE} if another image
|
||||||
* if another image is currently present.
|
* is currently present.
|
||||||
*
|
*
|
||||||
* @param visible whether the {@code attachmentView} should be displayed
|
* @param visible whether the {@code attachmentView} should be displayed
|
||||||
* @since Envoy Client v0.1-beta
|
* @since Envoy Client v0.1-beta
|
||||||
*/
|
*/
|
||||||
private void updateAttachmentView(boolean visible) {
|
private void updateAttachmentView(boolean visible) {
|
||||||
if (!attachmentView.getImage().equals(DEFAULT_ATTACHMENT_VIEW_IMAGE)) attachmentView.setImage(DEFAULT_ATTACHMENT_VIEW_IMAGE);
|
if (!(attachmentView.getImage() == null
|
||||||
|
|| attachmentView.getImage().equals(DEFAULT_ATTACHMENT_VIEW_IMAGE)))
|
||||||
|
attachmentView.setImage(DEFAULT_ATTACHMENT_VIEW_IMAGE);
|
||||||
attachmentView.setVisible(visible);
|
attachmentView.setVisible(visible);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Context menu actions
|
@Event(OwnStatusChange.class)
|
||||||
|
@Priority(50)
|
||||||
|
private void generateOwnStatusControl() {
|
||||||
|
|
||||||
@FXML
|
// Update the own user status if present
|
||||||
private void deleteContact() { try {} catch (final NullPointerException e) {} }
|
if (ownContactControl.getChildren().get(1) instanceof ContactControl)
|
||||||
|
((ContactControl) ownContactControl.getChildren().get(1)).replaceInfoLabel();
|
||||||
|
else {
|
||||||
|
|
||||||
|
// Else prepend it to the HBox children
|
||||||
|
final var ownUserControl = new ContactControl(localDB.getUser());
|
||||||
|
ownUserControl.setAlignment(Pos.CENTER_LEFT);
|
||||||
|
HBox.setHgrow(ownUserControl, javafx.scene.layout.Priority.NEVER);
|
||||||
|
ownContactControl.getChildren().add(1, ownUserControl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redesigns the UI when the {@link Chat} of the given contact has been marked as disabled.
|
||||||
|
*
|
||||||
|
* @param event the contact whose chat got disabled
|
||||||
|
* @since Envoy Client v0.3-beta
|
||||||
|
*/
|
||||||
|
@Event
|
||||||
|
public void disableChat(ContactDisabled event) {
|
||||||
|
chatList.refresh();
|
||||||
|
final var recipient = event.get();
|
||||||
|
|
||||||
|
// Decrement member count for groups
|
||||||
|
if (recipient instanceof Group)
|
||||||
|
topBarStatusLabel.setText(recipient.getContacts().size() + " member"
|
||||||
|
+ (recipient.getContacts().size() != 1 ? "s" : ""));
|
||||||
|
if (currentChat != null && currentChat.getRecipient().equals(recipient)) {
|
||||||
|
messageTextArea.setDisable(true);
|
||||||
|
voiceButton.setDisable(true);
|
||||||
|
attachmentButton.setDisable(true);
|
||||||
|
pendingAttachment = null;
|
||||||
|
messageList.getStyleClass().clear();
|
||||||
|
messageList.getStyleClass().add("disabled-chat");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets every component back to its inital state before a chat was selected.
|
||||||
|
*
|
||||||
|
* @since Envoy Client v0.3-beta
|
||||||
|
*/
|
||||||
|
public void resetState() {
|
||||||
|
currentChat = null;
|
||||||
|
chatList.getSelectionModel().clearSelection();
|
||||||
|
messageList.getItems().clear();
|
||||||
|
messageTextArea.setDisable(true);
|
||||||
|
attachmentView.setImage(null);
|
||||||
|
topBarContactLabel.setVisible(false);
|
||||||
|
topBarStatusLabel.setVisible(false);
|
||||||
|
messageSearchButton.setVisible(false);
|
||||||
|
messageTextArea.clear();
|
||||||
|
messageTextArea.setDisable(true);
|
||||||
|
attachmentButton.setDisable(true);
|
||||||
|
voiceButton.setDisable(true);
|
||||||
|
remainingChars.setVisible(false);
|
||||||
|
pendingAttachment = null;
|
||||||
|
recipientProfilePic.setImage(null);
|
||||||
|
if (recorder.isRecording())
|
||||||
|
recorder.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private void copyAndPostMessage() {
|
private void copyAndPostMessage() {
|
||||||
final var messageText = messageTextArea.getText();
|
final var messageText = messageTextArea.getText();
|
||||||
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(messageText), null);
|
Toolkit.getDefaultToolkit().getSystemClipboard()
|
||||||
|
.setContents(new StringSelection(messageText), null);
|
||||||
final var image = attachmentView.getImage();
|
final var image = attachmentView.getImage();
|
||||||
final var messageAttachment = pendingAttachment;
|
final var messageAttachment = pendingAttachment;
|
||||||
postMessage();
|
postMessage();
|
||||||
@ -749,13 +865,45 @@ public final class ChatScene implements EventListener, Restorable {
|
|||||||
updateRemainingCharsLabel();
|
updateRemainingCharsLabel();
|
||||||
postButton.setDisable(messageText.isBlank());
|
postButton.setDisable(messageText.isBlank());
|
||||||
attachmentView.setImage(image);
|
attachmentView.setImage(image);
|
||||||
if (attachmentView.getImage() != null) attachmentView.setVisible(true);
|
if (attachmentView.getImage() != null)
|
||||||
|
attachmentView.setVisible(true);
|
||||||
pendingAttachment = messageAttachment;
|
pendingAttachment = messageAttachment;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the current message selection.
|
||||||
|
*
|
||||||
|
* @since Envoy Client v0.3-beta
|
||||||
|
*/
|
||||||
|
public void clearMessageSelection() {
|
||||||
|
messageList.getSelectionModel().clearSelection();
|
||||||
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private void searchContacts() {
|
private void searchContacts() {
|
||||||
chats.setPredicate(contactSearch.getText().isBlank() ? c -> true
|
chats.setPredicate(contactSearch.getText().isBlank() ? c -> true
|
||||||
: c -> c.getRecipient().getName().toLowerCase().contains(contactSearch.getText().toLowerCase()));
|
: c -> c.getRecipient().getName().toLowerCase()
|
||||||
|
.contains(contactSearch.getText().toLowerCase()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<KeyCombination, Runnable> getKeyboardShortcuts() {
|
||||||
|
return Map.<KeyCombination, Runnable>of(
|
||||||
|
|
||||||
|
// Delete text before the caret with "Control" + U
|
||||||
|
new KeyCodeCombination(KeyCode.U, KeyCombination.CONTROL_DOWN), () -> {
|
||||||
|
messageTextArea
|
||||||
|
.setText(
|
||||||
|
messageTextArea.getText().substring(messageTextArea.getCaretPosition()));
|
||||||
|
checkPostConditions(false);
|
||||||
|
|
||||||
|
// Delete text after the caret with "Control" + K
|
||||||
|
}, new KeyCodeCombination(KeyCode.K, KeyCombination.CONTROL_DOWN), () -> {
|
||||||
|
messageTextArea
|
||||||
|
.setText(
|
||||||
|
messageTextArea.getText().substring(0, messageTextArea.getCaretPosition()));
|
||||||
|
checkPostConditions(false);
|
||||||
|
messageTextArea.positionCaret(messageTextArea.getText().length());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,34 +7,34 @@ import javafx.fxml.FXML;
|
|||||||
import javafx.scene.control.*;
|
import javafx.scene.control.*;
|
||||||
import javafx.scene.control.Alert.AlertType;
|
import javafx.scene.control.Alert.AlertType;
|
||||||
|
|
||||||
|
import dev.kske.eventbus.core.*;
|
||||||
|
|
||||||
|
import envoy.data.User;
|
||||||
|
import envoy.event.ElementOperation;
|
||||||
|
import envoy.event.contact.*;
|
||||||
|
import envoy.util.EnvoyLog;
|
||||||
|
|
||||||
import envoy.client.data.Context;
|
import envoy.client.data.Context;
|
||||||
import envoy.client.event.BackEvent;
|
import envoy.client.event.BackEvent;
|
||||||
import envoy.client.helper.AlertHelper;
|
import envoy.client.helper.AlertHelper;
|
||||||
import envoy.client.net.Client;
|
import envoy.client.net.Client;
|
||||||
import envoy.client.ui.control.ContactControl;
|
import envoy.client.ui.control.ContactControl;
|
||||||
import envoy.client.ui.listcell.ListCellFactory;
|
import envoy.client.ui.listcell.ListCellFactory;
|
||||||
import envoy.data.User;
|
|
||||||
import envoy.event.ElementOperation;
|
|
||||||
import envoy.event.contact.*;
|
|
||||||
import envoy.util.EnvoyLog;
|
|
||||||
|
|
||||||
import dev.kske.eventbus.*;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides a search bar in which a user name (substring) can be entered. The
|
* Provides a search bar in which a user name (substring) can be entered. The users with a matching
|
||||||
* users with a matching name are then displayed inside a list view. A
|
* name are then displayed inside a list view. A {@link UserSearchRequest} is sent on every
|
||||||
* {@link UserSearchRequest} is sent on every keystroke.
|
* keystroke.
|
||||||
* <p>
|
* <p>
|
||||||
* <i>The actual search algorithm is implemented on the server.
|
* <i>The actual search algorithm is implemented on the server.
|
||||||
* <p>
|
* <p>
|
||||||
* To create a group, a button is available that loads the
|
* To create a group, a button is available that loads the {@link GroupCreationTab}.
|
||||||
* {@link GroupCreationTab}.
|
|
||||||
*
|
*
|
||||||
* @author Leon Hofmeister
|
* @author Leon Hofmeister
|
||||||
* @author Maximilian Käfer
|
* @author Maximilian Käfer
|
||||||
* @since Envoy Client v0.1-beta
|
* @since Envoy Client v0.1-beta
|
||||||
*/
|
*/
|
||||||
public class ContactSearchTab implements EventListener {
|
public class ContactSearchTab {
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private TextArea searchBar;
|
private TextArea searchBar;
|
||||||
@ -59,15 +59,21 @@ public class ContactSearchTab implements EventListener {
|
|||||||
|
|
||||||
@Event
|
@Event
|
||||||
private void onUserSearchResult(UserSearchResult result) {
|
private void onUserSearchResult(UserSearchResult result) {
|
||||||
Platform.runLater(() -> { userList.getItems().clear(); userList.getItems().addAll(result.get()); });
|
Platform.runLater(() -> {
|
||||||
|
userList.getItems().clear();
|
||||||
|
userList.getItems().addAll(result.get());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Event
|
@Event
|
||||||
private void onContactOperation(ContactOperation operation) {
|
private void onUserOperation(UserOperation operation) {
|
||||||
final var contact = operation.get();
|
final var contact = operation.get();
|
||||||
if (operation.getOperationType() == ElementOperation.ADD) Platform.runLater(() -> {
|
if (operation.getOperationType() == ElementOperation.ADD)
|
||||||
|
Platform.runLater(() -> {
|
||||||
userList.getItems().remove(contact);
|
userList.getItems().remove(contact);
|
||||||
if (currentlySelectedUser != null && currentlySelectedUser.equals(contact) && alert.isShowing()) alert.close();
|
if (currentlySelectedUser != null && currentlySelectedUser.equals(contact)
|
||||||
|
&& alert.isShowing())
|
||||||
|
alert.close();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,13 +85,15 @@ public class ContactSearchTab implements EventListener {
|
|||||||
@FXML
|
@FXML
|
||||||
private void sendRequest() {
|
private void sendRequest() {
|
||||||
final var text = searchBar.getText().strip();
|
final var text = searchBar.getText().strip();
|
||||||
if (!text.isBlank()) client.send(new UserSearchRequest(text));
|
if (!text.isBlank())
|
||||||
else userList.getItems().clear();
|
client.send(new UserSearchRequest(text));
|
||||||
|
else
|
||||||
|
userList.getItems().clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clears the text in the search bar and the items shown in the list.
|
* Clears the text in the search bar and the items shown in the list. Additionally disables both
|
||||||
* Additionally disables both clear and search button.
|
* clear and search button.
|
||||||
*
|
*
|
||||||
* @since Envoy Client v0.1-beta
|
* @since Envoy Client v0.1-beta
|
||||||
*/
|
*/
|
||||||
@ -96,8 +104,7 @@ public class ContactSearchTab implements EventListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends an {@link ContactOperation} for the selected user to the
|
* Sends an {@link UserOperation} for the selected user to the server.
|
||||||
* server.
|
|
||||||
*
|
*
|
||||||
* @since Envoy Client v0.1-beta
|
* @since Envoy Client v0.1-beta
|
||||||
*/
|
*/
|
||||||
@ -106,7 +113,8 @@ public class ContactSearchTab implements EventListener {
|
|||||||
final var user = userList.getSelectionModel().getSelectedItem();
|
final var user = userList.getSelectionModel().getSelectedItem();
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
currentlySelectedUser = user;
|
currentlySelectedUser = user;
|
||||||
alert.setContentText("Add user " + currentlySelectedUser.getName() + " to your contacts?");
|
alert.setContentText(
|
||||||
|
"Add user " + currentlySelectedUser.getName() + " to your contacts?");
|
||||||
AlertHelper.confirmAction(alert, this::addAsContact);
|
AlertHelper.confirmAction(alert, this::addAsContact);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -114,7 +122,7 @@ public class ContactSearchTab implements EventListener {
|
|||||||
private void addAsContact() {
|
private void addAsContact() {
|
||||||
|
|
||||||
// Sends the event to the server
|
// Sends the event to the server
|
||||||
final var event = new ContactOperation(currentlySelectedUser, ElementOperation.ADD);
|
final var event = new UserOperation(currentlySelectedUser, ElementOperation.ADD);
|
||||||
client.send(event);
|
client.send(event);
|
||||||
|
|
||||||
// Removes the chosen user and updates the UI
|
// Removes the chosen user and updates the UI
|
||||||
@ -124,5 +132,8 @@ public class ContactSearchTab implements EventListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private void backButtonClicked() { eventBus.dispatch(new BackEvent()); }
|
private void backButtonClicked() {
|
||||||
|
searchBar.setText("");
|
||||||
|
eventBus.dispatch(new BackEvent());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,32 +7,33 @@ import java.util.stream.Collectors;
|
|||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.control.*;
|
import javafx.scene.control.*;
|
||||||
|
import javafx.scene.input.MouseEvent;
|
||||||
import javafx.scene.layout.HBox;
|
import javafx.scene.layout.HBox;
|
||||||
|
|
||||||
import envoy.client.data.*;
|
import dev.kske.eventbus.core.*;
|
||||||
import envoy.client.event.BackEvent;
|
|
||||||
import envoy.client.ui.control.ContactControl;
|
|
||||||
import envoy.client.ui.listcell.ListCellFactory;
|
|
||||||
import envoy.data.*;
|
import envoy.data.*;
|
||||||
import envoy.event.GroupCreation;
|
import envoy.event.GroupCreation;
|
||||||
import envoy.event.contact.ContactOperation;
|
import envoy.event.contact.UserOperation;
|
||||||
import envoy.util.Bounds;
|
import envoy.util.Bounds;
|
||||||
|
|
||||||
import dev.kske.eventbus.*;
|
import envoy.client.data.*;
|
||||||
|
import envoy.client.event.*;
|
||||||
|
import envoy.client.ui.control.*;
|
||||||
|
import envoy.client.ui.listcell.ListCellFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides a group creation interface. A group name can be entered in the text
|
* Provides a group creation interface. A group name can be entered in the text field at the top.
|
||||||
* field at the top. Available users (local chat recipients) are displayed
|
* Available users (local chat recipients) are displayed inside a list and can be selected (multiple
|
||||||
* inside a list and can be selected (multiple selection available).
|
* selection available).
|
||||||
* <p>
|
* <p>
|
||||||
* When the group creation button is pressed, a {@link GroupCreation} is sent to
|
* When the group creation button is pressed, a {@link GroupCreation} is sent to the server. This
|
||||||
* the server. This controller enforces a valid group name and a non-empty
|
* controller enforces a valid group name and a non-empty member list (excluding the client user).
|
||||||
* member list (excluding the client user).
|
|
||||||
*
|
*
|
||||||
* @author Maximilian Käfer
|
* @author Maximilian Käfer
|
||||||
* @since Envoy Client v0.1-beta
|
* @since Envoy Client v0.1-beta
|
||||||
*/
|
*/
|
||||||
public class GroupCreationTab implements EventListener {
|
public class GroupCreationTab {
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private Button createButton;
|
private Button createButton;
|
||||||
@ -58,6 +59,9 @@ public class GroupCreationTab implements EventListener {
|
|||||||
@FXML
|
@FXML
|
||||||
private HBox errorProceedBox;
|
private HBox errorProceedBox;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private ListView<QuickSelectControl> quickSelectList;
|
||||||
|
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
private final LocalDB localDB = Context.getInstance().getLocalDB();
|
private final LocalDB localDB = Context.getInstance().getLocalDB();
|
||||||
@ -67,7 +71,6 @@ public class GroupCreationTab implements EventListener {
|
|||||||
@FXML
|
@FXML
|
||||||
private void initialize() {
|
private void initialize() {
|
||||||
userList.setCellFactory(new ListCellFactory<>(ContactControl::new));
|
userList.setCellFactory(new ListCellFactory<>(ContactControl::new));
|
||||||
userList.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
|
|
||||||
createButton.setDisable(true);
|
createButton.setDisable(true);
|
||||||
eventBus.registerListener(this);
|
eventBus.registerListener(this);
|
||||||
userList.getItems()
|
userList.getItems()
|
||||||
@ -78,6 +81,8 @@ public class GroupCreationTab implements EventListener {
|
|||||||
.filter(not(localDB.getUser()::equals))
|
.filter(not(localDB.getUser()::equals))
|
||||||
.map(User.class::cast)
|
.map(User.class::cast)
|
||||||
.collect(Collectors.toList()));
|
.collect(Collectors.toList()));
|
||||||
|
resizeQuickSelectSpace(0);
|
||||||
|
quickSelectList.addEventFilter(MouseEvent.MOUSE_PRESSED, MouseEvent::consume);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -86,16 +91,29 @@ public class GroupCreationTab implements EventListener {
|
|||||||
* @since Envoy Client v0.1-beta
|
* @since Envoy Client v0.1-beta
|
||||||
*/
|
*/
|
||||||
@FXML
|
@FXML
|
||||||
private void userListClicked() { createButton.setDisable(userList.getSelectionModel().isEmpty() || groupNameField.getText().isBlank()); }
|
private void userListClicked() {
|
||||||
|
if (userList.getSelectionModel().getSelectedItem() != null) {
|
||||||
|
quickSelectList.getItems().add(new QuickSelectControl(
|
||||||
|
userList.getSelectionModel().getSelectedItem(), this::removeFromQuickSelection));
|
||||||
|
createButton.setDisable(
|
||||||
|
quickSelectList.getItems().isEmpty() || groupNameField.getText().isBlank());
|
||||||
|
resizeQuickSelectSpace(60);
|
||||||
|
userList.getItems().remove(userList.getSelectionModel().getSelectedItem());
|
||||||
|
userList.getSelectionModel().clearSelection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks, whether the {@code createButton} can be enabled because text is
|
* Checks, whether the {@code createButton} can be enabled because text is present in the text
|
||||||
* present in the text field.
|
* field.
|
||||||
*
|
*
|
||||||
* @since Envoy Client v0.1-beta
|
* @since Envoy Client v0.1-beta
|
||||||
*/
|
*/
|
||||||
@FXML
|
@FXML
|
||||||
private void textUpdated() { createButton.setDisable(userList.getSelectionModel().isEmpty() || groupNameField.getText().isBlank()); }
|
private void textUpdated() {
|
||||||
|
createButton
|
||||||
|
.setDisable(quickSelectList.getItems().isEmpty() || groupNameField.getText().isBlank());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends a {@link GroupCreation} to the server and closes this scene.
|
* Sends a {@link GroupCreation} to the server and closes this scene.
|
||||||
@ -123,6 +141,9 @@ public class GroupCreationTab implements EventListener {
|
|||||||
// Restoring the original design as tabs will always be reused
|
// Restoring the original design as tabs will always be reused
|
||||||
setErrorMessageLabelSize(0);
|
setErrorMessageLabelSize(0);
|
||||||
groupNameField.clear();
|
groupNameField.clear();
|
||||||
|
quickSelectList.getItems().forEach(q -> userList.getItems().add(q.getUser()));
|
||||||
|
quickSelectList.getItems().clear();
|
||||||
|
resizeQuickSelectSpace(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,19 +157,41 @@ public class GroupCreationTab implements EventListener {
|
|||||||
private void createGroup(String name) {
|
private void createGroup(String name) {
|
||||||
Context.getInstance()
|
Context.getInstance()
|
||||||
.getClient()
|
.getClient()
|
||||||
.send(new GroupCreation(name, userList.getSelectionModel().getSelectedItems().stream().map(User::getID).collect(Collectors.toSet())));
|
.send(new GroupCreation(name, quickSelectList.getItems().stream()
|
||||||
|
.map(q -> q.getUser().getID()).collect(Collectors.toSet())));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the proposed group name is already present in the users
|
* Returns true if the proposed group name is already present in the users {@code LocalDB}.
|
||||||
* {@code LocalDB}.
|
|
||||||
*
|
*
|
||||||
* @param newName the chosen group name
|
* @param newName the chosen group name
|
||||||
* @return true if this name is already present
|
* @return true if this name is already present
|
||||||
* @since Envoy Client v0.1-beta
|
* @since Envoy Client v0.1-beta
|
||||||
*/
|
*/
|
||||||
public boolean groupNameAlreadyPresent(String newName) {
|
public boolean groupNameAlreadyPresent(String newName) {
|
||||||
return localDB.getChats().stream().map(Chat::getRecipient).filter(Group.class::isInstance).map(Contact::getName).anyMatch(newName::equals);
|
return localDB.getChats().stream().map(Chat::getRecipient).filter(Group.class::isInstance)
|
||||||
|
.map(Contact::getName).anyMatch(newName::equals);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes an element from the quickSelectList.
|
||||||
|
*
|
||||||
|
* @param element the element to be removed.
|
||||||
|
* @since Envoy Client v0.3-beta
|
||||||
|
*/
|
||||||
|
public void removeFromQuickSelection(QuickSelectControl element) {
|
||||||
|
quickSelectList.getItems().remove(element);
|
||||||
|
userList.getItems().add(element.getUser());
|
||||||
|
if (quickSelectList.getItems().isEmpty()) {
|
||||||
|
resizeQuickSelectSpace(0);
|
||||||
|
createButton.setDisable(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resizeQuickSelectSpace(int value) {
|
||||||
|
quickSelectList.setPrefHeight(value);
|
||||||
|
quickSelectList.setMaxHeight(value);
|
||||||
|
quickSelectList.setMinHeight(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
@ -197,11 +240,11 @@ public class GroupCreationTab implements EventListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Event
|
@Event
|
||||||
private void onContactOperation(ContactOperation operation) {
|
private void onUserOperation(UserOperation operation) {
|
||||||
if (operation.get() instanceof User) Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
switch (operation.getOperationType()) {
|
switch (operation.getOperationType()) {
|
||||||
case ADD:
|
case ADD:
|
||||||
userList.getItems().add((User) operation.get());
|
userList.getItems().add(operation.get());
|
||||||
break;
|
break;
|
||||||
case REMOVE:
|
case REMOVE:
|
||||||
userList.getItems().removeIf(operation.get()::equals);
|
userList.getItems().removeIf(operation.get()::equals);
|
||||||
@ -209,4 +252,10 @@ public class GroupCreationTab implements EventListener {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Event
|
||||||
|
private void onAccountDeletion(AccountDeletion deletion) {
|
||||||
|
final var deletedID = deletion.get();
|
||||||
|
Platform.runLater(() -> userList.getItems().removeIf(user -> (user.getID() == deletedID)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,14 +10,15 @@ import javafx.scene.control.*;
|
|||||||
import javafx.scene.control.Alert.AlertType;
|
import javafx.scene.control.Alert.AlertType;
|
||||||
import javafx.scene.image.ImageView;
|
import javafx.scene.image.ImageView;
|
||||||
|
|
||||||
import envoy.client.data.ClientConfig;
|
import dev.kske.eventbus.core.*;
|
||||||
import envoy.client.ui.*;
|
|
||||||
import envoy.client.util.IconUtil;
|
|
||||||
import envoy.data.LoginCredentials;
|
import envoy.data.LoginCredentials;
|
||||||
import envoy.event.HandshakeRejection;
|
import envoy.event.HandshakeRejection;
|
||||||
import envoy.util.*;
|
import envoy.util.*;
|
||||||
|
|
||||||
import dev.kske.eventbus.*;
|
import envoy.client.data.ClientConfig;
|
||||||
|
import envoy.client.ui.Startup;
|
||||||
|
import envoy.client.util.IconUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Controller for the login scene.
|
* Controller for the login scene.
|
||||||
@ -26,7 +27,7 @@ import dev.kske.eventbus.*;
|
|||||||
* @author Maximilian Käfer
|
* @author Maximilian Käfer
|
||||||
* @since Envoy Client v0.1-beta
|
* @since Envoy Client v0.1-beta
|
||||||
*/
|
*/
|
||||||
public final class LoginScene implements EventListener {
|
public final class LoginScene {
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private TextField userTextField;
|
private TextField userTextField;
|
||||||
@ -78,25 +79,32 @@ public final class LoginScene implements EventListener {
|
|||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private void loginButtonPressed() {
|
private void loginButtonPressed() {
|
||||||
final String user = userTextField.getText(), pass = passwordField.getText(), repeatPass = repeatPasswordField.getText();
|
final String user = userTextField.getText(), pass = passwordField.getText(),
|
||||||
|
repeatPass = repeatPasswordField.getText();
|
||||||
final boolean requestToken = cbStaySignedIn.isSelected();
|
final boolean requestToken = cbStaySignedIn.isSelected();
|
||||||
|
|
||||||
// Prevent registration with unequal passwords
|
// Prevent registration with unequal passwords
|
||||||
if (registration && !pass.equals(repeatPass)) {
|
if (registration && !pass.equals(repeatPass)) {
|
||||||
new Alert(AlertType.ERROR, "The entered password is unequal to the repeated one").showAndWait();
|
new Alert(AlertType.ERROR, "The entered password is unequal to the repeated one")
|
||||||
|
.showAndWait();
|
||||||
repeatPasswordField.clear();
|
repeatPasswordField.clear();
|
||||||
} else if (!Bounds.isValidContactName(user)) {
|
} else if (!Bounds.isValidContactName(user)) {
|
||||||
new Alert(AlertType.ERROR, "The entered user name is not valid (" + Bounds.CONTACT_NAME_PATTERN + ")").showAndWait();
|
new Alert(AlertType.ERROR,
|
||||||
|
"The entered user name is not valid (" + Bounds.CONTACT_NAME_PATTERN + ")")
|
||||||
|
.showAndWait();
|
||||||
userTextField.clear();
|
userTextField.clear();
|
||||||
} else {
|
} else {
|
||||||
Instant lastSync = Startup.loadLastSync(userTextField.getText());
|
Instant lastSync = Startup.loadLastSync(userTextField.getText());
|
||||||
Startup.performHandshake(registration ? LoginCredentials.registration(user, pass, requestToken, Startup.VERSION, lastSync)
|
Startup.performHandshake(registration
|
||||||
|
? LoginCredentials.registration(user, pass, requestToken, Startup.VERSION, lastSync)
|
||||||
: LoginCredentials.login(user, pass, requestToken, Startup.VERSION, lastSync));
|
: LoginCredentials.login(user, pass, requestToken, Startup.VERSION, lastSync));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private void offlineModeButtonPressed() { Startup.attemptOfflineMode(userTextField.getText()); }
|
private void offlineModeButtonPressed() {
|
||||||
|
Startup.attemptOfflineMode(userTextField.getText());
|
||||||
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private void registerSwitchPressed() {
|
private void registerSwitchPressed() {
|
||||||
@ -127,5 +135,7 @@ public final class LoginScene implements EventListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Event
|
@Event
|
||||||
private void onHandshakeRejection(HandshakeRejection evt) { Platform.runLater(() -> new Alert(AlertType.ERROR, evt.get()).showAndWait()); }
|
private void onHandshakeRejection(HandshakeRejection evt) {
|
||||||
|
Platform.runLater(() -> new Alert(AlertType.ERROR, evt.get()).showAndWait());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
package envoy.client.ui.controller;
|
package envoy.client.ui.controller;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.control.*;
|
import javafx.scene.control.*;
|
||||||
|
import javafx.scene.input.*;
|
||||||
|
|
||||||
import envoy.client.data.Context;
|
import envoy.client.data.Context;
|
||||||
|
import envoy.client.data.shortcuts.KeyboardMapping;
|
||||||
import envoy.client.ui.listcell.ListCellFactory;
|
import envoy.client.ui.listcell.ListCellFactory;
|
||||||
import envoy.client.ui.settings.*;
|
import envoy.client.ui.settings.*;
|
||||||
|
|
||||||
@ -13,7 +17,7 @@ import envoy.client.ui.settings.*;
|
|||||||
* @author Kai S. K. Engelbart
|
* @author Kai S. K. Engelbart
|
||||||
* @since Envoy Client v0.1-beta
|
* @since Envoy Client v0.1-beta
|
||||||
*/
|
*/
|
||||||
public final class SettingsScene {
|
public final class SettingsScene implements KeyboardMapping {
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private ListView<SettingsPane> settingsList;
|
private ListView<SettingsPane> settingsList;
|
||||||
@ -24,7 +28,8 @@ public final class SettingsScene {
|
|||||||
@FXML
|
@FXML
|
||||||
private void initialize() {
|
private void initialize() {
|
||||||
settingsList.setCellFactory(new ListCellFactory<>(pane -> new Label(pane.getTitle())));
|
settingsList.setCellFactory(new ListCellFactory<>(pane -> new Label(pane.getTitle())));
|
||||||
settingsList.getItems().addAll(new GeneralSettingsPane(), new UserSettingsPane(), new DownloadSettingsPane(), new BugReportPane());
|
settingsList.getItems().addAll(new GeneralSettingsPane(), new UserSettingsPane(),
|
||||||
|
new DownloadSettingsPane(), new BugReportPane());
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
@ -37,5 +42,13 @@ public final class SettingsScene {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private void backButtonClicked() { Context.getInstance().getSceneContext().pop(); }
|
private void backButtonClicked() {
|
||||||
|
Context.getInstance().getSceneContext().pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<KeyCombination, Runnable> getKeyboardShortcuts() {
|
||||||
|
return Map.of(new KeyCodeCombination(KeyCode.B, KeyCombination.CONTROL_DOWN),
|
||||||
|
this::backButtonClicked);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,7 @@ public abstract class AbstractListCell<T, U extends Node> extends ListCell<T> {
|
|||||||
setGraphic(renderItem(item));
|
setGraphic(renderItem(item));
|
||||||
} else {
|
} else {
|
||||||
setGraphic(null);
|
setGraphic(null);
|
||||||
|
setCursor(Cursor.DEFAULT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,55 @@
|
|||||||
|
package envoy.client.ui.listcell;
|
||||||
|
|
||||||
|
import javafx.scene.control.*;
|
||||||
|
|
||||||
|
import envoy.data.User;
|
||||||
|
|
||||||
|
import envoy.client.data.*;
|
||||||
|
import envoy.client.net.Client;
|
||||||
|
import envoy.client.ui.control.ChatControl;
|
||||||
|
import envoy.client.util.UserUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list cell containing chats represented as chat controls.
|
||||||
|
*
|
||||||
|
* @author Leon Hofmeister
|
||||||
|
* @since Envoy Client v0.3-beta
|
||||||
|
*/
|
||||||
|
public class ChatListCell extends AbstractListCell<Chat, ChatControl> {
|
||||||
|
|
||||||
|
private static final Client client = Context.getInstance().getClient();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param listView the list view inside of which the cell will be displayed
|
||||||
|
* @since Envoy Client v0.3-beta
|
||||||
|
*/
|
||||||
|
public ChatListCell(ListView<? extends Chat> listView) {
|
||||||
|
super(listView);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ChatControl renderItem(Chat chat) {
|
||||||
|
if (client.isOnline()) {
|
||||||
|
final var menu = new ContextMenu();
|
||||||
|
final var removeMI = new MenuItem();
|
||||||
|
removeMI.setText(
|
||||||
|
chat.isDisabled() ? "Delete "
|
||||||
|
: chat.getRecipient() instanceof User ? "Block "
|
||||||
|
: "Leave group " + chat.getRecipient().getName());
|
||||||
|
removeMI.setOnAction(
|
||||||
|
chat.isDisabled() ? e -> UserUtil.deleteContact(chat.getRecipient())
|
||||||
|
: e -> UserUtil.disableContact(chat.getRecipient()));
|
||||||
|
menu.getItems().add(removeMI);
|
||||||
|
setContextMenu(menu);
|
||||||
|
} else
|
||||||
|
setContextMenu(null);
|
||||||
|
|
||||||
|
// TODO: replace with icon in ChatControl
|
||||||
|
final var chatControl = new ChatControl(chat);
|
||||||
|
if (chat.isDisabled())
|
||||||
|
chatControl.getStyleClass().add("disabled-chat");
|
||||||
|
else
|
||||||
|
chatControl.getStyleClass().remove("disabled-chat");
|
||||||
|
return chatControl;
|
||||||
|
}
|
||||||
|
}
|
@ -28,5 +28,7 @@ public final class GenericListCell<T, U extends Node> extends AbstractListCell<T
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected U renderItem(T item) { return renderer.apply(item); }
|
protected U renderItem(T item) {
|
||||||
|
return renderer.apply(item);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,15 +7,15 @@ import javafx.scene.control.*;
|
|||||||
import javafx.util.Callback;
|
import javafx.util.Callback;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides a creation mechanism for generic list cells given a list view and a
|
* Provides a creation mechanism for generic list cells given a list view and a conversion function.
|
||||||
* conversion function.
|
|
||||||
*
|
*
|
||||||
* @author Kai S. K. Engelbart
|
* @author Kai S. K. Engelbart
|
||||||
* @param <T> the type of object to display
|
* @param <T> the type of object to display
|
||||||
* @param <U> the type of node displayed
|
* @param <U> the type of node displayed
|
||||||
* @since Envoy Client v0.1-beta
|
* @since Envoy Client v0.1-beta
|
||||||
*/
|
*/
|
||||||
public final class ListCellFactory<T, U extends Node> implements Callback<ListView<T>, ListCell<T>> {
|
public final class ListCellFactory<T, U extends Node>
|
||||||
|
implements Callback<ListView<T>, ListCell<T>> {
|
||||||
|
|
||||||
private final Function<? super T, U> renderer;
|
private final Function<? super T, U> renderer;
|
||||||
|
|
||||||
@ -23,8 +23,12 @@ public final class ListCellFactory<T, U extends Node> implements Callback<ListVi
|
|||||||
* @param renderer a function converting the type to display into a node
|
* @param renderer a function converting the type to display into a node
|
||||||
* @since Envoy Client v0.1-beta
|
* @since Envoy Client v0.1-beta
|
||||||
*/
|
*/
|
||||||
public ListCellFactory(Function<? super T, U> renderer) { this.renderer = renderer; }
|
public ListCellFactory(Function<? super T, U> renderer) {
|
||||||
|
this.renderer = renderer;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ListCell<T> call(ListView<T> listView) { return new GenericListCell<>(listView, renderer); }
|
public ListCell<T> call(ListView<T> listView) {
|
||||||
|
return new GenericListCell<>(listView, renderer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,10 @@ package envoy.client.ui.listcell;
|
|||||||
import javafx.geometry.*;
|
import javafx.geometry.*;
|
||||||
import javafx.scene.control.ListView;
|
import javafx.scene.control.ListView;
|
||||||
|
|
||||||
import envoy.client.ui.control.MessageControl;
|
|
||||||
import envoy.data.Message;
|
import envoy.data.Message;
|
||||||
|
|
||||||
|
import envoy.client.ui.control.MessageControl;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A list cell containing messages represented as message controls.
|
* A list cell containing messages represented as message controls.
|
||||||
*
|
*
|
||||||
@ -18,20 +19,25 @@ public final class MessageListCell extends AbstractListCell<Message, MessageCont
|
|||||||
* @param listView the list view inside of which the cell will be displayed
|
* @param listView the list view inside of which the cell will be displayed
|
||||||
* @since Envoy Client v0.1-beta
|
* @since Envoy Client v0.1-beta
|
||||||
*/
|
*/
|
||||||
public MessageListCell(ListView<? extends Message> listView) { super(listView); }
|
public MessageListCell(ListView<? extends Message> listView) {
|
||||||
|
super(listView);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected MessageControl renderItem(Message message) {
|
protected MessageControl renderItem(Message message) {
|
||||||
final var control = new MessageControl(message);
|
final var control = new MessageControl(message);
|
||||||
listView.widthProperty().addListener((observable, oldValue, newValue) -> adjustPadding(newValue.intValue(), control.isOwnMessage()));
|
listView.widthProperty().addListener((observable, oldValue,
|
||||||
|
newValue) -> adjustPadding(newValue.intValue(), control.isOwnMessage()));
|
||||||
adjustPadding((int) listView.getWidth(), control.isOwnMessage());
|
adjustPadding((int) listView.getWidth(), control.isOwnMessage());
|
||||||
if (control.isOwnMessage()) setAlignment(Pos.CENTER_RIGHT);
|
if (control.isOwnMessage())
|
||||||
else setAlignment(Pos.CENTER_LEFT);
|
setAlignment(Pos.CENTER_RIGHT);
|
||||||
|
else
|
||||||
|
setAlignment(Pos.CENTER_LEFT);
|
||||||
return control;
|
return control;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void adjustPadding(int listWidth, boolean ownMessage) {
|
private void adjustPadding(int listWidth, boolean ownMessage) {
|
||||||
int padding = 10 + Math.max((listWidth - 1000) / 2, 0);
|
int padding = 10 + Math.max((listWidth - 1000) / 2, 0);
|
||||||
setPadding(ownMessage ? new Insets(0, padding, 6, 0) : new Insets(0, 0, 6, padding));
|
setPadding(ownMessage ? new Insets(3, padding, 3, 0) : new Insets(3, 0, 3, padding));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* This package contains custom list cells that are used to display certain
|
* This package contains custom list cells that are used to display certain things.
|
||||||
* things.
|
|
||||||
*
|
*
|
||||||
* @author Leon Hofmeister
|
* @author Leon Hofmeister
|
||||||
* @author Kai S. K. Engelbart
|
* @author Kai S. K. Engelbart
|
||||||
|
@ -7,8 +7,8 @@ import javafx.scene.input.InputEvent;
|
|||||||
import envoy.event.IssueProposal;
|
import envoy.event.IssueProposal;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class offers the option for users to submit a bug report. Only the title
|
* This class offers the option for users to submit a bug report. Only the title of a bug is needed
|
||||||
* of a bug is needed to be sent.
|
* to be sent.
|
||||||
*
|
*
|
||||||
* @author Leon Hofmeister
|
* @author Leon Hofmeister
|
||||||
* @since Envoy Client v0.2-beta
|
* @since Envoy Client v0.2-beta
|
||||||
@ -17,12 +17,15 @@ public final class BugReportPane extends OnlineOnlySettingsPane {
|
|||||||
|
|
||||||
private final Label titleLabel = new Label("Suggest a title for the bug:");
|
private final Label titleLabel = new Label("Suggest a title for the bug:");
|
||||||
private final TextField titleTextField = new TextField();
|
private final TextField titleTextField = new TextField();
|
||||||
private final Label pleaseExplainLabel = new Label("Paste here the log of what went wrong and/ or explain what went wrong:");
|
private final Label pleaseExplainLabel =
|
||||||
|
new Label("Paste here the log of what went wrong and/ or explain what went wrong:");
|
||||||
private final TextArea errorDetailArea = new TextArea();
|
private final TextArea errorDetailArea = new TextArea();
|
||||||
private final CheckBox showUsernameInBugReport = new CheckBox("Show your username in the bug report?");
|
private final CheckBox showUsernameInBugReport =
|
||||||
|
new CheckBox("Show your username in the bug report?");
|
||||||
private final Button submitReportButton = new Button("Submit report");
|
private final Button submitReportButton = new Button("Submit report");
|
||||||
|
|
||||||
private final EventHandler<? super InputEvent> inputEventHandler = e -> submitReportButton.setDisable(titleTextField.getText().isBlank());
|
private final EventHandler<? super InputEvent> inputEventHandler =
|
||||||
|
e -> submitReportButton.setDisable(titleTextField.getText().isBlank());
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new {@code BugReportPane}.
|
* Creates a new {@code BugReportPane}.
|
||||||
@ -59,7 +62,9 @@ public final class BugReportPane extends OnlineOnlySettingsPane {
|
|||||||
submitReportButton.setDisable(true);
|
submitReportButton.setDisable(true);
|
||||||
submitReportButton.setOnAction(e -> {
|
submitReportButton.setOnAction(e -> {
|
||||||
String title = titleTextField.getText(), description = errorDetailArea.getText();
|
String title = titleTextField.getText(), description = errorDetailArea.getText();
|
||||||
client.send(showUsernameInBugReport.isSelected() ? new IssueProposal(title, description, true) : new IssueProposal(title, description, client.getSender().getName(), true));
|
client.send(
|
||||||
|
showUsernameInBugReport.isSelected() ? new IssueProposal(title, description, true)
|
||||||
|
: new IssueProposal(title, description, client.getSender().getName(), true));
|
||||||
});
|
});
|
||||||
getChildren().add(submitReportButton);
|
getChildren().add(submitReportButton);
|
||||||
}
|
}
|
||||||
|
@ -5,8 +5,6 @@ import javafx.scene.control.*;
|
|||||||
import javafx.scene.layout.HBox;
|
import javafx.scene.layout.HBox;
|
||||||
import javafx.stage.DirectoryChooser;
|
import javafx.stage.DirectoryChooser;
|
||||||
|
|
||||||
import envoy.client.data.Context;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays options for downloading {@link envoy.data.Attachment}s.
|
* Displays options for downloading {@link envoy.data.Attachment}s.
|
||||||
*
|
*
|
||||||
@ -26,18 +24,22 @@ public final class DownloadSettingsPane extends SettingsPane {
|
|||||||
setPadding(new Insets(15));
|
setPadding(new Insets(15));
|
||||||
|
|
||||||
// Checkbox to disable asking
|
// Checkbox to disable asking
|
||||||
final var checkBox = new CheckBox(settings.getItems().get("autoSaveDownloads").getUserFriendlyName());
|
final var checkBox =
|
||||||
|
new CheckBox(settings.getItems().get("autoSaveDownloads").getUserFriendlyName());
|
||||||
checkBox.setSelected(settings.isDownloadSavedWithoutAsking());
|
checkBox.setSelected(settings.isDownloadSavedWithoutAsking());
|
||||||
checkBox.setTooltip(new Tooltip("Determines whether a \"Select save location\" - dialogue will be shown when saving attachments."));
|
checkBox.setTooltip(new Tooltip(
|
||||||
|
"Determines whether a \"Select save location\" - dialogue will be shown when saving attachments."));
|
||||||
checkBox.setOnAction(e -> settings.setDownloadSavedWithoutAsking(checkBox.isSelected()));
|
checkBox.setOnAction(e -> settings.setDownloadSavedWithoutAsking(checkBox.isSelected()));
|
||||||
getChildren().add(checkBox);
|
getChildren().add(checkBox);
|
||||||
|
|
||||||
// Displaying the default path to save to
|
// Displaying the default path to save to
|
||||||
final var pathLabel = new Label(settings.getItems().get("downloadLocation").getDescription() + ":");
|
final var pathLabel =
|
||||||
|
new Label(settings.getItems().get("downloadLocation").getDescription() + ":");
|
||||||
pathLabel.setWrapText(true);
|
pathLabel.setWrapText(true);
|
||||||
getChildren().add(pathLabel);
|
getChildren().add(pathLabel);
|
||||||
final var hbox = new HBox(20);
|
final var hbox = new HBox(20);
|
||||||
Tooltip.install(hbox, new Tooltip("Determines the location where attachments will be saved to."));
|
Tooltip.install(hbox,
|
||||||
|
new Tooltip("Determines the location where attachments will be saved to."));
|
||||||
final var currentPath = new Label(settings.getDownloadLocation().getAbsolutePath());
|
final var currentPath = new Label(settings.getDownloadLocation().getAbsolutePath());
|
||||||
hbox.getChildren().add(currentPath);
|
hbox.getChildren().add(currentPath);
|
||||||
|
|
||||||
@ -47,7 +49,8 @@ public final class DownloadSettingsPane extends SettingsPane {
|
|||||||
final var directoryChooser = new DirectoryChooser();
|
final var directoryChooser = new DirectoryChooser();
|
||||||
directoryChooser.setTitle("Select the directory where attachments should be saved to");
|
directoryChooser.setTitle("Select the directory where attachments should be saved to");
|
||||||
directoryChooser.setInitialDirectory(settings.getDownloadLocation());
|
directoryChooser.setInitialDirectory(settings.getDownloadLocation());
|
||||||
final var selectedDirectory = directoryChooser.showDialog(Context.getInstance().getSceneContext().getStage());
|
final var selectedDirectory =
|
||||||
|
directoryChooser.showDialog(context.getSceneContext().getStage());
|
||||||
|
|
||||||
if (selectedDirectory != null) {
|
if (selectedDirectory != null) {
|
||||||
currentPath.setText(selectedDirectory.getAbsolutePath());
|
currentPath.setText(selectedDirectory.getAbsolutePath());
|
||||||
|
@ -2,12 +2,14 @@ package envoy.client.ui.settings;
|
|||||||
|
|
||||||
import javafx.scene.control.*;
|
import javafx.scene.control.*;
|
||||||
|
|
||||||
import envoy.client.data.SettingsItem;
|
import dev.kske.eventbus.core.EventBus;
|
||||||
import envoy.client.event.ThemeChangeEvent;
|
|
||||||
import envoy.client.helper.ShutdownHelper;
|
|
||||||
import envoy.data.User.UserStatus;
|
import envoy.data.User.UserStatus;
|
||||||
|
|
||||||
import dev.kske.eventbus.EventBus;
|
import envoy.client.data.SettingsItem;
|
||||||
|
import envoy.client.event.ThemeChangeEvent;
|
||||||
|
import envoy.client.ui.StatusTrayIcon;
|
||||||
|
import envoy.client.util.UserUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Kai S. K. Engelbart
|
* @author Kai S. K. Engelbart
|
||||||
@ -22,23 +24,31 @@ public final class GeneralSettingsPane extends SettingsPane {
|
|||||||
super("General");
|
super("General");
|
||||||
setSpacing(10);
|
setSpacing(10);
|
||||||
|
|
||||||
// TODO: Support other value types
|
|
||||||
final var settingsItems = settings.getItems();
|
final var settingsItems = settings.getItems();
|
||||||
final var hideOnCloseCheckbox = new SettingsCheckbox((SettingsItem<Boolean>) settingsItems.get("hideOnClose"));
|
|
||||||
final var hideOnCloseTooltip = new Tooltip("If selected, Envoy will still be present in the task bar when closed.");
|
// Add hide on close if supported
|
||||||
|
final var hideOnCloseCheckbox =
|
||||||
|
new SettingsCheckbox((SettingsItem<Boolean>) settingsItems.get("hideOnClose"));
|
||||||
|
final var hideOnCloseTooltip = new Tooltip(StatusTrayIcon.isSupported()
|
||||||
|
? "If selected, Envoy will still be present in the task bar when closed."
|
||||||
|
: "status tray icon is not supported on your system.");
|
||||||
hideOnCloseTooltip.setWrapText(true);
|
hideOnCloseTooltip.setWrapText(true);
|
||||||
hideOnCloseCheckbox.setTooltip(hideOnCloseTooltip);
|
hideOnCloseCheckbox.setTooltip(hideOnCloseTooltip);
|
||||||
|
hideOnCloseCheckbox.setDisable(!StatusTrayIcon.isSupported());
|
||||||
getChildren().add(hideOnCloseCheckbox);
|
getChildren().add(hideOnCloseCheckbox);
|
||||||
|
|
||||||
final var enterToSendCheckbox = new SettingsCheckbox((SettingsItem<Boolean>) settingsItems.get("enterToSend"));
|
final var enterToSendCheckbox =
|
||||||
|
new SettingsCheckbox((SettingsItem<Boolean>) settingsItems.get("enterToSend"));
|
||||||
final var enterToSendTooltip = new Tooltip(
|
final var enterToSendTooltip = new Tooltip(
|
||||||
"When selected, messages can be sent pressing \"Enter\". A line break can be inserted by pressing \"Ctrl\" + \"Enter\". Else it will be the other way around.");
|
"When selected, messages can be sent pressing \"Enter\". A line break can be inserted by pressing \"Ctrl\" + \"Enter\". Else it will be the other way around.");
|
||||||
enterToSendTooltip.setWrapText(true);
|
enterToSendTooltip.setWrapText(true);
|
||||||
enterToSendCheckbox.setTooltip(enterToSendTooltip);
|
enterToSendCheckbox.setTooltip(enterToSendTooltip);
|
||||||
getChildren().add(enterToSendCheckbox);
|
getChildren().add(enterToSendCheckbox);
|
||||||
|
|
||||||
final var askForConfirmationCheckbox = new SettingsCheckbox((SettingsItem<Boolean>) settingsItems.get("askForConfirmation"));
|
final var askForConfirmationCheckbox =
|
||||||
final var askForConfirmationTooltip = new Tooltip("When selected, nothing will prompt a confirmation dialog");
|
new SettingsCheckbox((SettingsItem<Boolean>) settingsItems.get("askForConfirmation"));
|
||||||
|
final var askForConfirmationTooltip =
|
||||||
|
new Tooltip("When selected, nothing will prompt a confirmation dialog");
|
||||||
askForConfirmationTooltip.setWrapText(true);
|
askForConfirmationTooltip.setWrapText(true);
|
||||||
askForConfirmationCheckbox.setTooltip(askForConfirmationTooltip);
|
askForConfirmationCheckbox.setTooltip(askForConfirmationTooltip);
|
||||||
getChildren().add(askForConfirmationCheckbox);
|
getChildren().add(askForConfirmationCheckbox);
|
||||||
@ -46,22 +56,26 @@ public final class GeneralSettingsPane extends SettingsPane {
|
|||||||
final var combobox = new ComboBox<String>();
|
final var combobox = new ComboBox<String>();
|
||||||
combobox.getItems().add("dark");
|
combobox.getItems().add("dark");
|
||||||
combobox.getItems().add("light");
|
combobox.getItems().add("light");
|
||||||
combobox.setTooltip(new Tooltip("Determines the current theme Envoy will be displayed in."));
|
combobox
|
||||||
|
.setTooltip(new Tooltip("Determines the current theme Envoy will be displayed in."));
|
||||||
combobox.setValue(settings.getCurrentTheme());
|
combobox.setValue(settings.getCurrentTheme());
|
||||||
combobox.setOnAction(e -> { settings.setCurrentTheme(combobox.getValue()); EventBus.getInstance().dispatch(new ThemeChangeEvent()); });
|
combobox.setOnAction(e -> {
|
||||||
|
settings.setCurrentTheme(combobox.getValue());
|
||||||
|
EventBus.getInstance().dispatch(new ThemeChangeEvent());
|
||||||
|
});
|
||||||
getChildren().add(combobox);
|
getChildren().add(combobox);
|
||||||
|
|
||||||
final var statusComboBox = new ComboBox<UserStatus>();
|
final var statusComboBox = new ComboBox<UserStatus>();
|
||||||
statusComboBox.getItems().setAll(UserStatus.values());
|
statusComboBox.getItems().setAll(UserStatus.values());
|
||||||
statusComboBox.setValue(UserStatus.ONLINE);
|
statusComboBox.setValue(context.getLocalDB().getUser().getStatus());
|
||||||
statusComboBox.setTooltip(new Tooltip("Change your current status"));
|
statusComboBox.setTooltip(new Tooltip("Change your current status"));
|
||||||
// TODO add action when value is changed
|
statusComboBox.setOnAction(e -> UserUtil.changeStatus(statusComboBox.getValue()));
|
||||||
statusComboBox.setOnAction(e -> {});
|
|
||||||
getChildren().add(statusComboBox);
|
getChildren().add(statusComboBox);
|
||||||
|
|
||||||
final var logoutButton = new Button("Logout");
|
final var logoutButton = new Button("Logout");
|
||||||
logoutButton.setOnAction(e -> ShutdownHelper.logout());
|
logoutButton.setOnAction(e -> UserUtil.logout());
|
||||||
final var logoutTooltip = new Tooltip("Brings you back to the login screen and removes \"remember me\" status from this account");
|
final var logoutTooltip = new Tooltip(
|
||||||
|
"Brings you back to the login screen and removes \"remember me\" status from this account");
|
||||||
logoutTooltip.setWrapText(true);
|
logoutTooltip.setWrapText(true);
|
||||||
logoutButton.setTooltip(logoutTooltip);
|
logoutButton.setTooltip(logoutTooltip);
|
||||||
getChildren().add(logoutButton);
|
getChildren().add(logoutButton);
|
||||||
|
@ -5,14 +5,13 @@ import javafx.scene.control.*;
|
|||||||
import javafx.scene.layout.*;
|
import javafx.scene.layout.*;
|
||||||
import javafx.scene.paint.Color;
|
import javafx.scene.paint.Color;
|
||||||
|
|
||||||
import envoy.client.data.Context;
|
|
||||||
import envoy.client.net.Client;
|
import envoy.client.net.Client;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inheriting from this class signifies that options should only be available if
|
* Inheriting from this class signifies that options should only be available if the
|
||||||
* the {@link envoy.data.User} is currently online. If the user is currently
|
* {@link envoy.data.User} is currently online. If the user is currently offline, all
|
||||||
* offline, all {@link javafx.scene.Node} variables will be disabled and a
|
* {@link javafx.scene.Node} variables will be disabled and a {@link Tooltip} will be displayed for
|
||||||
* {@link Tooltip} will be displayed for the whole node.
|
* the whole node.
|
||||||
*
|
*
|
||||||
* @author Leon Hofmeister
|
* @author Leon Hofmeister
|
||||||
* @author Kai S. K. Engelbart
|
* @author Kai S. K. Engelbart
|
||||||
@ -20,9 +19,10 @@ import envoy.client.net.Client;
|
|||||||
*/
|
*/
|
||||||
public abstract class OnlineOnlySettingsPane extends SettingsPane {
|
public abstract class OnlineOnlySettingsPane extends SettingsPane {
|
||||||
|
|
||||||
protected final Client client = Context.getInstance().getClient();
|
protected final Client client = context.getClient();
|
||||||
|
|
||||||
private final Tooltip beOnlineReminder = new Tooltip("You need to be online to modify your account.");
|
private final Tooltip beOnlineReminder =
|
||||||
|
new Tooltip("You need to be online to modify your account.");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param title the title of this pane
|
* @param title the title of this pane
|
||||||
@ -34,14 +34,17 @@ public abstract class OnlineOnlySettingsPane extends SettingsPane {
|
|||||||
setDisable(!client.isOnline());
|
setDisable(!client.isOnline());
|
||||||
|
|
||||||
if (!client.isOnline()) {
|
if (!client.isOnline()) {
|
||||||
final var infoLabel = new Label("You shall not pass!\n(... Unless you would happen to be online)");
|
final var infoLabel =
|
||||||
|
new Label("You shall not pass!\n(... Unless you would happen to be online)");
|
||||||
infoLabel.setId("info-label-warning");
|
infoLabel.setId("info-label-warning");
|
||||||
infoLabel.setWrapText(true);
|
infoLabel.setWrapText(true);
|
||||||
getChildren().add(infoLabel);
|
getChildren().add(infoLabel);
|
||||||
setBackground(new Background(new BackgroundFill(Color.grayRgb(100, 0.3), CornerRadii.EMPTY, Insets.EMPTY)));
|
setBackground(new Background(
|
||||||
|
new BackgroundFill(Color.grayRgb(100, 0.3), CornerRadii.EMPTY, Insets.EMPTY)));
|
||||||
|
|
||||||
Tooltip.install(this, beOnlineReminder);
|
Tooltip.install(this, beOnlineReminder);
|
||||||
} else Tooltip.uninstall(this, beOnlineReminder);
|
} else
|
||||||
|
Tooltip.uninstall(this, beOnlineReminder);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -50,5 +53,7 @@ public abstract class OnlineOnlySettingsPane extends SettingsPane {
|
|||||||
* @param text the text to display
|
* @param text the text to display
|
||||||
* @since Envoy Client v0.2-beta
|
* @since Envoy Client v0.2-beta
|
||||||
*/
|
*/
|
||||||
protected void setToolTipText(String text) { beOnlineReminder.setText(text); }
|
protected void setToolTipText(String text) {
|
||||||
|
beOnlineReminder.setText(text);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ package envoy.client.ui.settings;
|
|||||||
|
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
|
|
||||||
import envoy.client.data.Settings;
|
import envoy.client.data.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Kai S. K. Engelbart
|
* @author Kai S. K. Engelbart
|
||||||
@ -13,8 +13,11 @@ public abstract class SettingsPane extends VBox {
|
|||||||
protected String title;
|
protected String title;
|
||||||
|
|
||||||
protected static final Settings settings = Settings.getInstance();
|
protected static final Settings settings = Settings.getInstance();
|
||||||
|
protected static final Context context = Context.getInstance();
|
||||||
|
|
||||||
protected SettingsPane(String title) { this.title = title; }
|
protected SettingsPane(String title) {
|
||||||
|
this.title = title;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the title of this settings pane
|
* @return the title of this settings pane
|
||||||
|
@ -13,14 +13,15 @@ import javafx.scene.image.*;
|
|||||||
import javafx.scene.input.InputEvent;
|
import javafx.scene.input.InputEvent;
|
||||||
import javafx.scene.layout.HBox;
|
import javafx.scene.layout.HBox;
|
||||||
import javafx.stage.FileChooser;
|
import javafx.stage.FileChooser;
|
||||||
|
import javafx.util.Duration;
|
||||||
|
|
||||||
|
import dev.kske.eventbus.core.EventBus;
|
||||||
|
|
||||||
import envoy.client.data.Context;
|
|
||||||
import envoy.client.ui.control.ProfilePicImageView;
|
|
||||||
import envoy.client.util.IconUtil;
|
|
||||||
import envoy.event.*;
|
import envoy.event.*;
|
||||||
import envoy.util.*;
|
import envoy.util.*;
|
||||||
|
|
||||||
import dev.kske.eventbus.EventBus;
|
import envoy.client.ui.control.ProfilePicImageView;
|
||||||
|
import envoy.client.util.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Leon Hofmeister
|
* @author Leon Hofmeister
|
||||||
@ -38,6 +39,7 @@ public final class UserSettingsPane extends OnlineOnlySettingsPane {
|
|||||||
private final PasswordField newPasswordField = new PasswordField();
|
private final PasswordField newPasswordField = new PasswordField();
|
||||||
private final PasswordField repeatNewPasswordField = new PasswordField();
|
private final PasswordField repeatNewPasswordField = new PasswordField();
|
||||||
private final Button saveButton = new Button("Save");
|
private final Button saveButton = new Button("Save");
|
||||||
|
private final Button deleteAccountButton = new Button("Delete Account (Locally)");
|
||||||
|
|
||||||
private static final EventBus eventBus = EventBus.getInstance();
|
private static final EventBus eventBus = EventBus.getInstance();
|
||||||
private static final Logger logger = EnvoyLog.getLogger(UserSettingsPane.class);
|
private static final Logger logger = EnvoyLog.getLogger(UserSettingsPane.class);
|
||||||
@ -59,21 +61,24 @@ public final class UserSettingsPane extends OnlineOnlySettingsPane {
|
|||||||
profilePic.setFitWidth(60);
|
profilePic.setFitWidth(60);
|
||||||
profilePic.setFitHeight(60);
|
profilePic.setFitHeight(60);
|
||||||
profilePic.setOnMouseClicked(e -> {
|
profilePic.setOnMouseClicked(e -> {
|
||||||
if (!client.isOnline()) return;
|
if (!client.isOnline())
|
||||||
|
return;
|
||||||
final var pictureChooser = new FileChooser();
|
final var pictureChooser = new FileChooser();
|
||||||
|
|
||||||
pictureChooser.setTitle("Select a new profile pic");
|
pictureChooser.setTitle("Select a new profile pic");
|
||||||
pictureChooser.setInitialDirectory(new File(System.getProperty("user.home")));
|
pictureChooser.setInitialDirectory(new File(System.getProperty("user.home")));
|
||||||
pictureChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("Pictures", "*.png", "*.jpg", "*.bmp", "*.gif"));
|
pictureChooser.getExtensionFilters().add(
|
||||||
|
new FileChooser.ExtensionFilter("Pictures", "*.png", "*.jpg", "*.bmp", "*.gif"));
|
||||||
|
|
||||||
final var file = pictureChooser.showOpenDialog(Context.getInstance().getSceneContext().getStage());
|
final var file = pictureChooser.showOpenDialog(context.getSceneContext().getStage());
|
||||||
|
|
||||||
if (file != null) {
|
if (file != null) {
|
||||||
|
|
||||||
// Check max file size
|
// Check max file size
|
||||||
// TODO: Move to config
|
// TODO: Move to config
|
||||||
if (file.length() > 5E6) {
|
if (file.length() > 5E6) {
|
||||||
new Alert(AlertType.WARNING, "The selected file exceeds the size limit of 5MB!").showAndWait();
|
new Alert(AlertType.WARNING, "The selected file exceeds the size limit of 5MB!")
|
||||||
|
.showAndWait();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,14 +108,24 @@ public final class UserSettingsPane extends OnlineOnlySettingsPane {
|
|||||||
|
|
||||||
// "Displaying" the password change mechanism
|
// "Displaying" the password change mechanism
|
||||||
final HBox[] passwordHBoxes = { new HBox(), new HBox(), new HBox() };
|
final HBox[] passwordHBoxes = { new HBox(), new HBox(), new HBox() };
|
||||||
final Label[] passwordLabels = { new Label("Enter current password:"), new Label("Enter new password:"),
|
final Label[] passwordLabels =
|
||||||
|
{ new Label("Enter current password:"), new Label("Enter new password:"),
|
||||||
new Label("Repeat new password:") };
|
new Label("Repeat new password:") };
|
||||||
|
|
||||||
final PasswordField[] passwordFields = { currentPasswordField, newPasswordField, repeatNewPasswordField };
|
final PasswordField[] passwordFields =
|
||||||
final EventHandler<? super InputEvent> passwordEntered = e -> {
|
{ currentPasswordField, newPasswordField, repeatNewPasswordField };
|
||||||
newPassword = newPasswordField.getText();
|
final EventHandler<? super InputEvent> passwordEntered =
|
||||||
validPassword = newPassword.equals(repeatNewPasswordField.getText())
|
e -> {
|
||||||
&& !newPasswordField.getText().isBlank();
|
newPassword =
|
||||||
|
newPasswordField
|
||||||
|
.getText();
|
||||||
|
validPassword =
|
||||||
|
newPassword.equals(
|
||||||
|
repeatNewPasswordField
|
||||||
|
.getText())
|
||||||
|
&& !newPasswordField
|
||||||
|
.getText()
|
||||||
|
.isBlank();
|
||||||
};
|
};
|
||||||
newPasswordField.setOnInputMethodTextChanged(passwordEntered);
|
newPasswordField.setOnInputMethodTextChanged(passwordEntered);
|
||||||
newPasswordField.setOnKeyTyped(passwordEntered);
|
newPasswordField.setOnKeyTyped(passwordEntered);
|
||||||
@ -126,9 +141,22 @@ public final class UserSettingsPane extends OnlineOnlySettingsPane {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Displaying the save button
|
// Displaying the save button
|
||||||
saveButton.setOnAction(e -> save(client.getSender().getID(), currentPasswordField.getText()));
|
saveButton
|
||||||
|
.setOnAction(e -> save(currentPasswordField.getText()));
|
||||||
saveButton.setAlignment(Pos.BOTTOM_RIGHT);
|
saveButton.setAlignment(Pos.BOTTOM_RIGHT);
|
||||||
getChildren().add(saveButton);
|
getChildren().add(saveButton);
|
||||||
|
|
||||||
|
// Displaying the delete account button
|
||||||
|
deleteAccountButton.setAlignment(Pos.BASELINE_CENTER);
|
||||||
|
deleteAccountButton.setOnAction(e -> UserUtil.deleteAccount());
|
||||||
|
deleteAccountButton.setText("Delete Account (locally)");
|
||||||
|
deleteAccountButton.setPrefHeight(25);
|
||||||
|
deleteAccountButton.getStyleClass().clear();
|
||||||
|
deleteAccountButton.getStyleClass().add("danger-button");
|
||||||
|
final var tooltip = new Tooltip("Remote deletion is currently unsupported.");
|
||||||
|
tooltip.setShowDelay(Duration.millis(100));
|
||||||
|
deleteAccountButton.setTooltip(tooltip);
|
||||||
|
getChildren().add(deleteAccountButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -137,11 +165,11 @@ public final class UserSettingsPane extends OnlineOnlySettingsPane {
|
|||||||
* @param username the new username
|
* @param username the new username
|
||||||
* @since Envoy Client v0.2-beta
|
* @since Envoy Client v0.2-beta
|
||||||
*/
|
*/
|
||||||
private void save(long userID, String oldPassword) {
|
private void save(String oldPassword) {
|
||||||
|
|
||||||
// The profile pic was changed
|
// The profile pic was changed
|
||||||
if (profilePicChanged) {
|
if (profilePicChanged) {
|
||||||
final var profilePicChangeEvent = new ProfilePicChange(currentImageBytes, userID);
|
final var profilePicChangeEvent = new ProfilePicChange(currentImageBytes);
|
||||||
eventBus.dispatch(profilePicChangeEvent);
|
eventBus.dispatch(profilePicChangeEvent);
|
||||||
client.send(profilePicChangeEvent);
|
client.send(profilePicChangeEvent);
|
||||||
logger.log(Level.INFO, "The user just changed his profile pic.");
|
logger.log(Level.INFO, "The user just changed his profile pic.");
|
||||||
@ -150,14 +178,16 @@ public final class UserSettingsPane extends OnlineOnlySettingsPane {
|
|||||||
// The username was changed
|
// The username was changed
|
||||||
final var validContactName = Bounds.isValidContactName(newUsername);
|
final var validContactName = Bounds.isValidContactName(newUsername);
|
||||||
if (usernameChanged && validContactName) {
|
if (usernameChanged && validContactName) {
|
||||||
final var nameChangeEvent = new NameChange(userID, newUsername);
|
final var nameChangeEvent = new NameChange(client.getSender().getID(), newUsername);
|
||||||
eventBus.dispatch(nameChangeEvent);
|
eventBus.dispatch(nameChangeEvent);
|
||||||
client.send(nameChangeEvent);
|
client.send(nameChangeEvent);
|
||||||
logger.log(Level.INFO, "The user just changed his name to " + newUsername + ".");
|
logger.log(Level.INFO, "The user just changed his name to " + newUsername + ".");
|
||||||
} else if (!validContactName) {
|
} else if (!validContactName) {
|
||||||
final var alert = new Alert(AlertType.ERROR);
|
final var alert = new Alert(AlertType.ERROR);
|
||||||
alert.setTitle("Invalid username");
|
alert.setTitle("Invalid username");
|
||||||
alert.setContentText("The entered username does not conform with the naming limitations: " + Bounds.CONTACT_NAME_PATTERN);
|
alert.setContentText(
|
||||||
|
"The entered username does not conform with the naming limitations: "
|
||||||
|
+ Bounds.CONTACT_NAME_PATTERN);
|
||||||
alert.showAndWait();
|
alert.showAndWait();
|
||||||
logger.log(Level.INFO, "An invalid username was requested.");
|
logger.log(Level.INFO, "An invalid username was requested.");
|
||||||
return;
|
return;
|
||||||
@ -165,7 +195,7 @@ public final class UserSettingsPane extends OnlineOnlySettingsPane {
|
|||||||
|
|
||||||
// The password was changed
|
// The password was changed
|
||||||
if (validPassword) {
|
if (validPassword) {
|
||||||
client.send(new PasswordChangeRequest(newPassword, oldPassword, userID));
|
client.send(new PasswordChangeRequest(newPassword, oldPassword));
|
||||||
logger.log(Level.INFO, "The user just tried to change his password!");
|
logger.log(Level.INFO, "The user just tried to change his password!");
|
||||||
} else if (!(validPassword || newPassword.isBlank())) {
|
} else if (!(validPassword || newPassword.isBlank())) {
|
||||||
final var alert = new Alert(AlertType.ERROR);
|
final var alert = new Alert(AlertType.ERROR);
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* This package contains classes used for representing the settings
|
* This package contains classes used for representing the settings visually.
|
||||||
* visually.
|
|
||||||
*
|
*
|
||||||
* @author Leon Hofmeister
|
* @author Leon Hofmeister
|
||||||
* @author Kai S. K. Engelbart
|
* @author Kai S. K. Engelbart
|
||||||
|
@ -9,18 +9,22 @@ import javax.imageio.ImageIO;
|
|||||||
|
|
||||||
import javafx.scene.image.Image;
|
import javafx.scene.image.Image;
|
||||||
|
|
||||||
import envoy.client.data.Settings;
|
|
||||||
import envoy.util.EnvoyLog;
|
import envoy.util.EnvoyLog;
|
||||||
|
|
||||||
|
import envoy.client.data.Settings;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides static utility methods for loading icons from the resource
|
* Provides static utility methods for loading icons from the resource folder.
|
||||||
* folder.
|
|
||||||
*
|
*
|
||||||
* @author Kai S. K. Engelbart
|
* @author Kai S. K. Engelbart
|
||||||
* @since Envoy Client v0.1-beta
|
* @since Envoy Client v0.1-beta
|
||||||
*/
|
*/
|
||||||
public final class IconUtil {
|
public final class IconUtil {
|
||||||
|
|
||||||
|
private static final HashMap<String, Image> cache = new HashMap<>();
|
||||||
|
private static final HashMap<String, Image> scaledCache = new HashMap<>();
|
||||||
|
private static final HashMap<String, BufferedImage> awtCache = new HashMap<>();
|
||||||
|
|
||||||
private IconUtil() {}
|
private IconUtil() {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -30,7 +34,10 @@ public final class IconUtil {
|
|||||||
* @return the loaded image
|
* @return the loaded image
|
||||||
* @since Envoy Client v0.1-beta
|
* @since Envoy Client v0.1-beta
|
||||||
*/
|
*/
|
||||||
public static Image load(String path) { return new Image(IconUtil.class.getResource(path).toExternalForm()); }
|
public static Image load(String path) {
|
||||||
|
return cache.computeIfAbsent(path,
|
||||||
|
p -> new Image(IconUtil.class.getResource(p).toExternalForm()));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads an image from the resource folder and scales it to the given size.
|
* Loads an image from the resource folder and scales it to the given size.
|
||||||
@ -41,12 +48,14 @@ public final class IconUtil {
|
|||||||
* @since Envoy Client v0.1-beta
|
* @since Envoy Client v0.1-beta
|
||||||
*/
|
*/
|
||||||
public static Image load(String path, int size) {
|
public static Image load(String path, int size) {
|
||||||
return new Image(IconUtil.class.getResource(path).toExternalForm(), size, size, true, true);
|
return scaledCache.computeIfAbsent(path + size,
|
||||||
|
p -> new Image(IconUtil.class.getResource(path).toExternalForm(), size, size, true,
|
||||||
|
true));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads a {@code .png} image from the sub-folder {@code /icons/} of the
|
* Loads a {@code .png} image from the sub-folder {@code /icons/} of the resource folder.
|
||||||
* resource folder.<br>
|
* <p>
|
||||||
* The suffix {@code .png} is automatically appended.
|
* The suffix {@code .png} is automatically appended.
|
||||||
*
|
*
|
||||||
* @param name the image name without the .png suffix
|
* @param name the image name without the .png suffix
|
||||||
@ -55,11 +64,13 @@ public final class IconUtil {
|
|||||||
* @apiNote let's load a sample image {@code /icons/abc.png}.<br>
|
* @apiNote let's load a sample image {@code /icons/abc.png}.<br>
|
||||||
* To do that, we only have to call {@code IconUtil.loadIcon("abc")}
|
* To do that, we only have to call {@code IconUtil.loadIcon("abc")}
|
||||||
*/
|
*/
|
||||||
public static Image loadIcon(String name) { return load("/icons/" + name + ".png"); }
|
public static Image loadIcon(String name) {
|
||||||
|
return load("/icons/" + name + ".png");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads a {@code .png} image from the sub-folder {@code /icons/} of the
|
* Loads a {@code .png} image from the sub-folder {@code /icons/} of the resource folder and
|
||||||
* resource folder and scales it to the given size.<br>
|
* scales it to the given size.<br>
|
||||||
* The suffix {@code .png} is automatically appended.
|
* The suffix {@code .png} is automatically appended.
|
||||||
*
|
*
|
||||||
* @param name the image name without the .png suffix
|
* @param name the image name without the .png suffix
|
||||||
@ -67,20 +78,19 @@ public final class IconUtil {
|
|||||||
* @return the loaded image
|
* @return the loaded image
|
||||||
* @since Envoy Client v0.1-beta
|
* @since Envoy Client v0.1-beta
|
||||||
* @apiNote let's load a sample image {@code /icons/abc.png} in size 16.<br>
|
* @apiNote let's load a sample image {@code /icons/abc.png} in size 16.<br>
|
||||||
* To do that, we only have to call
|
* To do that, we only have to call {@code IconUtil.loadIcon("abc", 16)}
|
||||||
* {@code IconUtil.loadIcon("abc", 16)}
|
|
||||||
*/
|
*/
|
||||||
public static Image loadIcon(String name, int size) { return load("/icons/" + name + ".png", size); }
|
public static Image loadIcon(String name, int size) {
|
||||||
|
return load("/icons/" + name + ".png", size);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads a {@code .png} image whose design depends on the currently active theme
|
* Loads a {@code .png} image whose design depends on the currently active theme from the
|
||||||
* from the sub-folder {@code /icons/dark/} or {@code /icons/light/} of the
|
* sub-folder {@code /icons/dark/} or {@code /icons/light/} of the resource folder.
|
||||||
* resource folder.
|
|
||||||
* <p>
|
* <p>
|
||||||
* The suffix {@code .png} is automatically appended.
|
* The suffix {@code .png} is automatically appended.
|
||||||
*
|
*
|
||||||
* @param name the image name without the "black" or "white" suffix and without
|
* @param name the image name without the "black" or "white" suffix and without the .png suffix
|
||||||
* the .png suffix
|
|
||||||
* @return the loaded image
|
* @return the loaded image
|
||||||
* @since Envoy Client v0.1-beta
|
* @since Envoy Client v0.1-beta
|
||||||
* @apiNote let's take two sample images {@code /icons/dark/abc.png} and
|
* @apiNote let's take two sample images {@code /icons/dark/abc.png} and
|
||||||
@ -88,12 +98,14 @@ public final class IconUtil {
|
|||||||
* To do that theme sensitive, we only have to call
|
* To do that theme sensitive, we only have to call
|
||||||
* {@code IconUtil.loadIconThemeSensitive("abc")}
|
* {@code IconUtil.loadIconThemeSensitive("abc")}
|
||||||
*/
|
*/
|
||||||
public static Image loadIconThemeSensitive(String name) { return loadIcon(themeSpecificSubFolder() + name); }
|
public static Image loadIconThemeSensitive(String name) {
|
||||||
|
return loadIcon(themeSpecificSubFolder() + name);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads a {@code .png} image whose design depends on the currently active theme
|
* Loads a {@code .png} image whose design depends on the currently active theme from the
|
||||||
* from the sub-folder {@code /icons/dark/} or {@code /icons/light/} of the
|
* sub-folder {@code /icons/dark/} or {@code /icons/light/} of the resource folder and scales it
|
||||||
* resource folder and scales it to the given size.
|
* to the given size.
|
||||||
* <p>
|
* <p>
|
||||||
* The suffix {@code .png} is automatically appended.
|
* The suffix {@code .png} is automatically appended.
|
||||||
*
|
*
|
||||||
@ -106,20 +118,19 @@ public final class IconUtil {
|
|||||||
* To do that theme sensitive, we only have to call
|
* To do that theme sensitive, we only have to call
|
||||||
* {@code IconUtil.loadIconThemeSensitive("abc", 16)}
|
* {@code IconUtil.loadIconThemeSensitive("abc", 16)}
|
||||||
*/
|
*/
|
||||||
public static Image loadIconThemeSensitive(String name, int size) { return loadIcon(themeSpecificSubFolder() + name, size); }
|
public static Image loadIconThemeSensitive(String name, int size) {
|
||||||
|
return loadIcon(themeSpecificSubFolder() + name, size);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Loads images specified by an enum. The images have to be named like the lowercase enum
|
||||||
* Loads images specified by an enum. The images have to be named like the
|
* constants with {@code .png} extension and be located inside a folder with the lowercase name
|
||||||
* lowercase enum constants with {@code .png} extension and be located inside a
|
* of the enum, which must be contained inside the {@code /icons/} folder.
|
||||||
* folder with the lowercase name of the enum, which must be contained inside
|
|
||||||
* the {@code /icons/} folder.
|
|
||||||
*
|
*
|
||||||
* @param <T> the enum that specifies the images to load
|
* @param <T> the enum that specifies the images to load
|
||||||
* @param enumClass the class of the enum
|
* @param enumClass the class of the enum
|
||||||
* @param size the size to scale the images to
|
* @param size the size to scale the images to
|
||||||
* @return a map containing the loaded images with the corresponding enum
|
* @return a map containing the loaded images with the corresponding enum constants as keys
|
||||||
* constants as keys
|
|
||||||
* @since Envoy Client v0.1-beta
|
* @since Envoy Client v0.1-beta
|
||||||
*/
|
*/
|
||||||
public static <T extends Enum<T>> EnumMap<T, Image> loadByEnum(Class<T> enumClass, int size) {
|
public static <T extends Enum<T>> EnumMap<T, Image> loadByEnum(Class<T> enumClass, int size) {
|
||||||
@ -138,25 +149,29 @@ public final class IconUtil {
|
|||||||
* @since Envoy Client v0.2-beta
|
* @since Envoy Client v0.2-beta
|
||||||
*/
|
*/
|
||||||
public static BufferedImage loadAWTCompatible(String path) {
|
public static BufferedImage loadAWTCompatible(String path) {
|
||||||
BufferedImage image = null;
|
return awtCache.computeIfAbsent(path, p -> {
|
||||||
try {
|
try {
|
||||||
image = ImageIO.read(IconUtil.class.getResource(path));
|
return ImageIO.read(IconUtil.class.getResource(path));
|
||||||
} catch (final IOException e) {
|
} catch (IOException e) {
|
||||||
EnvoyLog.getLogger(IconUtil.class).log(Level.WARNING, String.format("Could not load image at path %s: ", path), e);
|
EnvoyLog.getLogger(IconUtil.class).log(Level.WARNING,
|
||||||
|
String.format("Could not load image at path %s: ", path), e);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
return image;
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method should be called if the display of an image depends upon the
|
* This method should be called if the display of an image depends upon the currently active
|
||||||
* currently active theme.<br>
|
* theme.<br>
|
||||||
* In case of a default theme, the string returned will be
|
* In case of a default theme, the string returned will be ({@code dark/} or {@code light/}),
|
||||||
* ({@code dark/} or {@code light/}), otherwise it will be empty.
|
* otherwise it will be empty.
|
||||||
*
|
*
|
||||||
* @return the theme specific folder
|
* @return the theme specific folder
|
||||||
* @since Envoy Client v0.1-beta
|
* @since Envoy Client v0.1-beta
|
||||||
*/
|
*/
|
||||||
private static String themeSpecificSubFolder() {
|
private static String themeSpecificSubFolder() {
|
||||||
return Settings.getInstance().isUsingDefaultTheme() ? Settings.getInstance().getCurrentTheme() + "/" : "";
|
return Settings.getInstance().isUsingDefaultTheme()
|
||||||
|
? Settings.getInstance().getCurrentTheme() + "/"
|
||||||
|
: "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
116
client/src/main/java/envoy/client/util/MessageUtil.java
Normal file
116
client/src/main/java/envoy/client/util/MessageUtil.java
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
package envoy.client.util;
|
||||||
|
|
||||||
|
import java.awt.Toolkit;
|
||||||
|
import java.awt.datatransfer.StringSelection;
|
||||||
|
import java.io.*;
|
||||||
|
import java.util.logging.*;
|
||||||
|
|
||||||
|
import javafx.stage.FileChooser;
|
||||||
|
|
||||||
|
import dev.kske.eventbus.core.EventBus;
|
||||||
|
|
||||||
|
import envoy.data.Message;
|
||||||
|
import envoy.util.EnvoyLog;
|
||||||
|
|
||||||
|
import envoy.client.data.*;
|
||||||
|
import envoy.client.event.MessageDeletion;
|
||||||
|
import envoy.client.ui.controller.ChatScene;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains methods that are commonly used for {@link Message}s.
|
||||||
|
*
|
||||||
|
* @author Leon Hofmeister
|
||||||
|
* @since Envoy Client v0.3-beta
|
||||||
|
*/
|
||||||
|
public class MessageUtil {
|
||||||
|
|
||||||
|
private MessageUtil() {}
|
||||||
|
|
||||||
|
private static Logger logger = EnvoyLog.getLogger(MessageUtil.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copies the text of the given message to the System Clipboard.
|
||||||
|
*
|
||||||
|
* @param message the message whose text to copy
|
||||||
|
* @since Envoy Client v0.3-beta
|
||||||
|
*/
|
||||||
|
public static void copyMessageText(Message message) {
|
||||||
|
logger.log(Level.FINEST,
|
||||||
|
"A copy of message text \"" + message.getText() + "\" was requested");
|
||||||
|
Toolkit.getDefaultToolkit().getSystemClipboard()
|
||||||
|
.setContents(new StringSelection(message.getText()), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the given message.
|
||||||
|
*
|
||||||
|
* @param message the message to delete
|
||||||
|
* @since Envoy Client v0.3-beta
|
||||||
|
*/
|
||||||
|
public static void deleteMessage(Message message) {
|
||||||
|
final var messageDeletionEvent = new MessageDeletion(message.getID());
|
||||||
|
final var controller =
|
||||||
|
Context.getInstance().getSceneContext().getController();
|
||||||
|
if (controller instanceof ChatScene)
|
||||||
|
((ChatScene) controller).clearMessageSelection();
|
||||||
|
|
||||||
|
// Removing the message locally
|
||||||
|
EventBus.getInstance().dispatch(messageDeletionEvent);
|
||||||
|
|
||||||
|
logger.log(Level.FINEST, "message deletion was requested for " + message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forwards the given message. Currently not implemented.
|
||||||
|
*
|
||||||
|
* @param message the message to forward
|
||||||
|
* @since Envoy Client v0.3-beta
|
||||||
|
*/
|
||||||
|
public static void forwardMessage(Message message) {
|
||||||
|
logger.log(Level.FINEST, "Message forwarding was requested for " + message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Quotes the given message. Currently not implemented.
|
||||||
|
*
|
||||||
|
* @param message the message to quote
|
||||||
|
* @since Envoy Client v0.3-beta
|
||||||
|
*/
|
||||||
|
public static void quoteMessage(Message message) {
|
||||||
|
logger.log(Level.FINEST, "Message quotation was requested for " + message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves the attachment of a message, if present.
|
||||||
|
*
|
||||||
|
* @param message the message whose attachment to save
|
||||||
|
* @throws IllegalStateException if no attachment is present in the message
|
||||||
|
* @since Envoy Client v0.3-beta
|
||||||
|
*/
|
||||||
|
public static void saveAttachment(Message message) {
|
||||||
|
if (!message.hasAttachment())
|
||||||
|
throw new IllegalArgumentException("Cannot save a non-existing attachment");
|
||||||
|
File file;
|
||||||
|
final var fileName = message.getAttachment().getName();
|
||||||
|
final var downloadLocation = Settings.getInstance().getDownloadLocation();
|
||||||
|
|
||||||
|
// Show save file dialog, if the user did not opt-out
|
||||||
|
if (!Settings.getInstance().isDownloadSavedWithoutAsking()) {
|
||||||
|
final var fileChooser = new FileChooser();
|
||||||
|
fileChooser.setInitialFileName(fileName);
|
||||||
|
fileChooser.setInitialDirectory(downloadLocation);
|
||||||
|
file = fileChooser.showSaveDialog(Context.getInstance().getSceneContext().getStage());
|
||||||
|
} else
|
||||||
|
file = new File(downloadLocation, fileName);
|
||||||
|
|
||||||
|
// A file was selected
|
||||||
|
if (file != null)
|
||||||
|
try (var fos = new FileOutputStream(file)) {
|
||||||
|
fos.write(message.getAttachment().getData());
|
||||||
|
logger.log(Level.FINE,
|
||||||
|
"Attachment of message was saved at " + file.getAbsolutePath());
|
||||||
|
} catch (final IOException e) {
|
||||||
|
logger.log(Level.WARNING, "Could not save attachment of " + message + ": ", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -14,26 +14,25 @@ public final class ReflectionUtil {
|
|||||||
private ReflectionUtil() {}
|
private ReflectionUtil() {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets all declared variable values of the given instance that have the
|
* Gets all declared variable values of the given instance that have the specified class.
|
||||||
* specified class.
|
|
||||||
* <p>
|
* <p>
|
||||||
* (i.e. can get all {@code JComponents} (Swing) or {@code Nodes} (JavaFX) in a
|
* (i.e. can get all {@code JComponents} (Swing) or {@code Nodes} (JavaFX) in a GUI class).
|
||||||
* GUI class).
|
|
||||||
* <p>
|
* <p>
|
||||||
* <b>Important: If you are using a module, you first need to declare <br>
|
* <b>Important: If you are using a module, you first need to declare <br>
|
||||||
* "opens {your_package} to envoy.client.util;" in your module-info.java</b>.
|
* "opens {your_package} to envoy.client.util;" in your module-info.java</b>.
|
||||||
*
|
*
|
||||||
* @param <T> the type of the object
|
* @param <T> the type of the object
|
||||||
* @param <R> the type to return
|
* @param <R> the type to return
|
||||||
* @param instance the instance of a given class whose values are to be
|
* @param instance the instance of a given class whose values are to be evaluated
|
||||||
* evaluated
|
|
||||||
* @param typeToReturn the type of variable to return
|
* @param typeToReturn the type of variable to return
|
||||||
* @return all variables in the given instance that have the requested type
|
* @return all variables in the given instance that have the requested type
|
||||||
* @throws RuntimeException if an exception occurs
|
* @throws RuntimeException if an exception occurs
|
||||||
* @since Envoy Client v0.2-beta
|
* @since Envoy Client v0.2-beta
|
||||||
*/
|
*/
|
||||||
public static <T, R> Stream<R> getAllDeclaredVariablesOfTypeAsStream(T instance, Class<R> typeToReturn) {
|
public static <T, R> Stream<R> getAllDeclaredVariablesOfTypeAsStream(T instance,
|
||||||
return Arrays.stream(instance.getClass().getDeclaredFields()).filter(field -> typeToReturn.isAssignableFrom(field.getType())).map(field -> {
|
Class<R> typeToReturn) {
|
||||||
|
return Arrays.stream(instance.getClass().getDeclaredFields())
|
||||||
|
.filter(field -> typeToReturn.isAssignableFrom(field.getType())).map(field -> {
|
||||||
try {
|
try {
|
||||||
field.setAccessible(true);
|
field.setAccessible(true);
|
||||||
return typeToReturn.cast(field.get(instance));
|
return typeToReturn.cast(field.get(instance));
|
||||||
@ -44,17 +43,14 @@ public final class ReflectionUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets all declared variables of the given instance that are children of
|
* Gets all declared variables of the given instance that are children of {@code Node}.
|
||||||
* {@code Node}.
|
|
||||||
* <p>
|
* <p>
|
||||||
* <b>Important: If you are using a module, you first need to declare <br>
|
* <b>Important: If you are using a module, you first need to declare <br>
|
||||||
* "opens {your_package} to envoy.client.util;" in your module-info.java</b>.
|
* "opens {your_package} to envoy.client.util;" in your module-info.java</b>.
|
||||||
*
|
*
|
||||||
* @param <T> the type of the instance
|
* @param <T> the type of the instance
|
||||||
* @param instance the instance of a given class whose values are to be
|
* @param instance the instance of a given class whose values are to be evaluated
|
||||||
* evaluated
|
* @return all variables of the given object that have the requested type as {@code Stream}
|
||||||
* @return all variables of the given object that have the requested type as
|
|
||||||
* {@code Stream}
|
|
||||||
* @since Envoy Client v0.2-beta
|
* @since Envoy Client v0.2-beta
|
||||||
*/
|
*/
|
||||||
public static <T> Stream<Node> getAllDeclaredNodeVariablesAsStream(T instance) {
|
public static <T> Stream<Node> getAllDeclaredNodeVariablesAsStream(T instance) {
|
||||||
@ -62,15 +58,13 @@ public final class ReflectionUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets all declared variables of the given instance that are children of
|
* Gets all declared variables of the given instance that are children of {@code Node}<br>
|
||||||
* {@code Node}<br>
|
|
||||||
* <p>
|
* <p>
|
||||||
* <b>Important: If you are using a module, you first need to declare <br>
|
* <b>Important: If you are using a module, you first need to declare <br>
|
||||||
* "opens {your_package} to envoy.client.util;" in your module-info.java</b>.
|
* "opens {your_package} to envoy.client.util;" in your module-info.java</b>.
|
||||||
*
|
*
|
||||||
* @param <T> the type of the instance
|
* @param <T> the type of the instance
|
||||||
* @param instance the instance of a given class whose values are to be
|
* @param instance the instance of a given class whose values are to be evaluated
|
||||||
* evaluated
|
|
||||||
* @return all variables of the given object that have the requested type
|
* @return all variables of the given object that have the requested type
|
||||||
* @since Envoy Client v0.2-beta
|
* @since Envoy Client v0.2-beta
|
||||||
*/
|
*/
|
||||||
|
155
client/src/main/java/envoy/client/util/UserUtil.java
Normal file
155
client/src/main/java/envoy/client/util/UserUtil.java
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
package envoy.client.util;
|
||||||
|
|
||||||
|
import java.util.logging.*;
|
||||||
|
|
||||||
|
import javafx.scene.control.*;
|
||||||
|
import javafx.scene.control.Alert.AlertType;
|
||||||
|
|
||||||
|
import dev.kske.eventbus.core.EventBus;
|
||||||
|
|
||||||
|
import envoy.data.*;
|
||||||
|
import envoy.data.User.UserStatus;
|
||||||
|
import envoy.event.*;
|
||||||
|
import envoy.event.contact.UserOperation;
|
||||||
|
import envoy.util.EnvoyLog;
|
||||||
|
|
||||||
|
import envoy.client.data.Context;
|
||||||
|
import envoy.client.event.*;
|
||||||
|
import envoy.client.helper.*;
|
||||||
|
import envoy.client.ui.SceneInfo;
|
||||||
|
import envoy.client.ui.controller.ChatScene;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains methods that change something about the currently logged in user.
|
||||||
|
*
|
||||||
|
* @author Leon Hofmeister
|
||||||
|
* @since Envoy Client v0.3-beta
|
||||||
|
*/
|
||||||
|
public final class UserUtil {
|
||||||
|
|
||||||
|
private static final Context context = Context.getInstance();
|
||||||
|
private static final Logger logger = EnvoyLog.getLogger(UserUtil.class);
|
||||||
|
|
||||||
|
private UserUtil() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs the current user out and reopens {@link envoy.client.ui.controller.LoginScene}.
|
||||||
|
*
|
||||||
|
* @since Envoy Client v0.2-beta
|
||||||
|
*/
|
||||||
|
public static void logout() {
|
||||||
|
final var alert = new Alert(AlertType.CONFIRMATION);
|
||||||
|
alert.setTitle("Logout?");
|
||||||
|
alert.setContentText("Are you sure you want to log out?");
|
||||||
|
|
||||||
|
AlertHelper.confirmAction(alert, () -> {
|
||||||
|
EnvoyLog.getLogger(ShutdownHelper.class).log(Level.INFO, "A logout was requested");
|
||||||
|
EventBus.getInstance().dispatch(new EnvoyCloseEvent());
|
||||||
|
EventBus.getInstance().dispatch(new Logout());
|
||||||
|
context.getSceneContext().load(SceneInfo.LOGIN_SCENE);
|
||||||
|
logger.log(Level.INFO, "A logout occurred.");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies the application that the status of the currently logged in user has changed.
|
||||||
|
*
|
||||||
|
* @param newStatus the new status
|
||||||
|
* @since Envoy Client v0.3-beta
|
||||||
|
*/
|
||||||
|
public static void changeStatus(UserStatus newStatus) {
|
||||||
|
|
||||||
|
// Sending the already active status is a valid action
|
||||||
|
if (newStatus.equals(context.getLocalDB().getUser().getStatus()))
|
||||||
|
return;
|
||||||
|
else {
|
||||||
|
EventBus.getInstance().dispatch(new OwnStatusChange(newStatus));
|
||||||
|
if (context.getClient().isOnline())
|
||||||
|
context.getClient()
|
||||||
|
.send(new UserStatusChange(context.getLocalDB().getUser().getID(), newStatus));
|
||||||
|
logger.log(Level.INFO, "A manual status change occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the given contact.
|
||||||
|
*
|
||||||
|
* @param block the contact that should be removed
|
||||||
|
* @since Envoy Client v0.3-beta
|
||||||
|
*/
|
||||||
|
public static void disableContact(Contact block) {
|
||||||
|
if (!context.getClient().isOnline() || block == null)
|
||||||
|
return;
|
||||||
|
else {
|
||||||
|
final var alert = new Alert(AlertType.CONFIRMATION);
|
||||||
|
alert.setContentText("Are you sure you want to "
|
||||||
|
+ (block instanceof User ? "block " : "leave group ") + block.getName() + "?");
|
||||||
|
AlertHelper.confirmAction(alert, () -> {
|
||||||
|
final var isUser = block instanceof User;
|
||||||
|
context.getClient()
|
||||||
|
.send(isUser ? new UserOperation((User) block, ElementOperation.REMOVE)
|
||||||
|
: new GroupResize(context.getLocalDB().getUser(), (Group) block,
|
||||||
|
ElementOperation.REMOVE));
|
||||||
|
if (!isUser)
|
||||||
|
block.getContacts().remove(context.getLocalDB().getUser());
|
||||||
|
EventBus.getInstance().dispatch(new ContactDisabled(block));
|
||||||
|
logger.log(Level.INFO, isUser ? "A user was blocked." : "The user left a group.");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the given contact with all his messages entirely.
|
||||||
|
*
|
||||||
|
* @param delete the contact to delete
|
||||||
|
* @since Envoy Client v0.3-beta
|
||||||
|
*/
|
||||||
|
public static void deleteContact(Contact delete) {
|
||||||
|
if (delete == null)
|
||||||
|
return;
|
||||||
|
else {
|
||||||
|
final var alert = new Alert(AlertType.CONFIRMATION);
|
||||||
|
alert.setContentText("Are you sure you want to delete " + delete.getName()
|
||||||
|
+ " entirely? All messages with this contact will be deleted. This action cannot be undone.");
|
||||||
|
AlertHelper.confirmAction(alert, () -> {
|
||||||
|
context.getLocalDB().getUsers().remove(delete.getName());
|
||||||
|
context.getLocalDB().getChats()
|
||||||
|
.removeIf(chat -> chat.getRecipient().equals(delete));
|
||||||
|
if (context.getSceneContext().getController() instanceof ChatScene)
|
||||||
|
((ChatScene) context.getSceneContext().getController()).resetState();
|
||||||
|
logger.log(Level.INFO, "A contact with all his messages was deleted.");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes anything pointing to this user, independent of client or server. Will do nothing if
|
||||||
|
* the client is currently offline.
|
||||||
|
*
|
||||||
|
* @since Envoy Client v0.3-beta
|
||||||
|
*/
|
||||||
|
public static void deleteAccount() {
|
||||||
|
|
||||||
|
// Show the first wall of defense, if not disabled by the user
|
||||||
|
final var outerAlert = new Alert(AlertType.CONFIRMATION);
|
||||||
|
outerAlert.setContentText(
|
||||||
|
"Are you sure you want to delete your account entirely? This action can seriously not be undone.");
|
||||||
|
outerAlert.setTitle("Delete Account?");
|
||||||
|
AlertHelper.confirmAction(outerAlert, () -> {
|
||||||
|
|
||||||
|
// Show the final wall of defense in every case
|
||||||
|
final var lastAlert = new Alert(AlertType.WARNING,
|
||||||
|
"Do you REALLY want to delete your account? Last Warning. Proceed?",
|
||||||
|
ButtonType.CANCEL, ButtonType.OK);
|
||||||
|
lastAlert.setTitle("Delete Account?");
|
||||||
|
lastAlert.showAndWait().filter(ButtonType.OK::equals).ifPresent(b2 -> {
|
||||||
|
|
||||||
|
// Delete the account
|
||||||
|
// TODO: Notify server of account deletion
|
||||||
|
context.getLocalDB().delete();
|
||||||
|
logger.log(Level.INFO, "The user just deleted his account. Goodbye.");
|
||||||
|
ShutdownHelper.exit(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* This module contains all classes defining the client application of the Envoy
|
* This module contains all classes defining the client application of the Envoy project.
|
||||||
* project.
|
|
||||||
*
|
*
|
||||||
* @author Kai S. K. Engelbart
|
* @author Kai S. K. Engelbart
|
||||||
* @author Leon Hofmeister
|
* @author Leon Hofmeister
|
||||||
@ -17,11 +16,13 @@ module envoy.client {
|
|||||||
requires javafx.fxml;
|
requires javafx.fxml;
|
||||||
requires javafx.base;
|
requires javafx.base;
|
||||||
requires javafx.graphics;
|
requires javafx.graphics;
|
||||||
|
requires dev.kske.eventbus.core;
|
||||||
|
|
||||||
opens envoy.client.ui to javafx.graphics, javafx.fxml, dev.kske.eventbus;
|
opens envoy.client.ui to javafx.graphics, javafx.fxml, dev.kske.eventbus.core;
|
||||||
opens envoy.client.ui.controller to javafx.graphics, javafx.fxml, envoy.client.util, dev.kske.eventbus;
|
opens envoy.client.ui.controller to javafx.graphics, javafx.fxml, envoy.client.util, dev.kske.eventbus.core;
|
||||||
|
opens envoy.client.ui.chatscene to javafx.graphics, javafx.fxml, envoy.client.util, dev.kske.eventbus.core;
|
||||||
opens envoy.client.ui.control to javafx.graphics, javafx.fxml;
|
opens envoy.client.ui.control to javafx.graphics, javafx.fxml;
|
||||||
opens envoy.client.ui.settings to envoy.client.util;
|
opens envoy.client.ui.settings to envoy.client.util;
|
||||||
opens envoy.client.net to dev.kske.eventbus;
|
opens envoy.client.net to dev.kske.eventbus.core;
|
||||||
opens envoy.client.data to dev.kske.eventbus;
|
opens envoy.client.data to dev.kske.eventbus.core;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
|
fileLevelBarrier=OFF
|
||||||
|
consoleLevelBarrier=FINER
|
||||||
server=localhost
|
server=localhost
|
||||||
port=8080
|
port=8080
|
||||||
localDB=localDB
|
|
||||||
localDBSaveInterval=2
|
localDBSaveInterval=2
|
||||||
consoleLevelBarrier=FINER
|
|
||||||
fileLevelBarrier=OFF
|
|
||||||
|
@ -4,8 +4,10 @@
|
|||||||
|
|
||||||
.context-menu, .context-menu > * {
|
.context-menu, .context-menu > * {
|
||||||
-fx-background-radius: 15.0px;
|
-fx-background-radius: 15.0px;
|
||||||
/*TODO: solution below does not work */
|
}
|
||||||
-fx-background-color: transparent;
|
|
||||||
|
.list-cell:selected, .menu-item:hover, .combo-box-popup .list-view .list-cell:selected {
|
||||||
|
-fx-background-color: #454c4f;
|
||||||
}
|
}
|
||||||
|
|
||||||
#text-enter-container, #contact-search-enter-container {
|
#text-enter-container, #contact-search-enter-container {
|
||||||
@ -39,7 +41,7 @@
|
|||||||
-fx-scale-y: 1.05;
|
-fx-scale-y: 1.05;
|
||||||
}
|
}
|
||||||
|
|
||||||
.label {
|
.label, .quick-select {
|
||||||
-fx-background-color: transparent;
|
-fx-background-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,6 +70,17 @@
|
|||||||
-fx-text-fill: gray;
|
-fx-text-fill: gray;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.danger-button {
|
||||||
|
-fx-background-color: red;
|
||||||
|
-fx-text-fill: white;
|
||||||
|
-fx-background-radius: 0.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.danger-button:hover {
|
||||||
|
-fx-scale-x: 1.05;
|
||||||
|
-fx-scale-y: 1.05;
|
||||||
|
}
|
||||||
|
|
||||||
.received-message {
|
.received-message {
|
||||||
-fx-alignment: center-left;
|
-fx-alignment: center-left;
|
||||||
-fx-background-radius: 1.3em;
|
-fx-background-radius: 1.3em;
|
||||||
@ -138,7 +151,24 @@
|
|||||||
.tab-pane {
|
.tab-pane {
|
||||||
-fx-tab-max-height: 0.0 ;
|
-fx-tab-max-height: 0.0 ;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab-pane .tab-header-area {
|
.tab-pane .tab-header-area {
|
||||||
visibility: hidden ;
|
visibility: hidden ;
|
||||||
-fx-padding: -20.0 0.0 0.0 0.0;
|
-fx-padding: -20.0 0.0 0.0 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.disabled-chat {
|
||||||
|
-fx-background-color: #0000FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
#quick-select-list .scroll-bar:horizontal{
|
||||||
|
-fx-pref-height: 0.0;
|
||||||
|
-fx-max-height: 0.0;
|
||||||
|
-fx-min-height: 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#quick-select-list .scroll-bar:vertical{
|
||||||
|
-fx-pref-width: 0.0;
|
||||||
|
-fx-max-width: 0.0;
|
||||||
|
-fx-min-width: 0.0;
|
||||||
|
}
|
||||||
|
@ -18,14 +18,10 @@
|
|||||||
-fx-background-color: lightgray;
|
-fx-background-color: lightgray;
|
||||||
}
|
}
|
||||||
|
|
||||||
#message-list, .text-field, .password-field, .tooltip, .pane, .pane .content, .vbox, .titled-pane > .title, .titled-pane > *.content, .context-menu, .menu-item {
|
#message-list, .text-field, .password-field, .tooltip, .pane, .pane .content, .vbox, .titled-pane > .title, .titled-pane > *.content, .context-menu, .menu-item, .combo-box-popup .list-view .list-cell, #quick-select-list {
|
||||||
-fx-background-color: #222222;
|
-fx-background-color: #222222;
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-cell:selected, .list-cell:selected > *, .menu-item:hover {
|
|
||||||
-fx-background-color: #690099;
|
|
||||||
}
|
|
||||||
|
|
||||||
.received-message {
|
.received-message {
|
||||||
-fx-background-color: gray;
|
-fx-background-color: gray;
|
||||||
}
|
}
|
||||||
@ -73,7 +69,7 @@
|
|||||||
-fx-background-color: transparent;
|
-fx-background-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.scroll-bar:vertical .increment-arrow, .scroll-bar:vertical .decrement-arrow {
|
.scroll-bar:vertical .increment-arrow, .scroll-bar:vertical .decrement-arrow, .list-cell {
|
||||||
-fx-background-color: transparent;
|
-fx-background-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,3 +83,8 @@
|
|||||||
-fx-text-fill: white;
|
-fx-text-fill: white;
|
||||||
-fx-background-color: transparent;
|
-fx-background-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#remove-button {
|
||||||
|
-fx-background-color: red;
|
||||||
|
-fx-background-radius: 1em;
|
||||||
|
}
|
||||||
|
@ -18,10 +18,6 @@
|
|||||||
-fx-background-color: #E3E3E3;
|
-fx-background-color: #E3E3E3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-cell:selected, .list-cell:selected > *, .menu-item:hover {
|
|
||||||
-fx-background-color: #805959;
|
|
||||||
}
|
|
||||||
|
|
||||||
.received-message {
|
.received-message {
|
||||||
-fx-background-color: lightgray;
|
-fx-background-color: lightgray;
|
||||||
}
|
}
|
||||||
@ -34,6 +30,10 @@
|
|||||||
-fx-background-color: black;
|
-fx-background-color: black;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tooltip {
|
||||||
|
-fx-text-fill: black;
|
||||||
|
}
|
||||||
|
|
||||||
#login-input-field {
|
#login-input-field {
|
||||||
-fx-border-color: black;
|
-fx-border-color: black;
|
||||||
}
|
}
|
||||||
|
@ -20,13 +20,18 @@
|
|||||||
<?import javafx.scene.layout.RowConstraints?>
|
<?import javafx.scene.layout.RowConstraints?>
|
||||||
<?import javafx.scene.layout.VBox?>
|
<?import javafx.scene.layout.VBox?>
|
||||||
<?import javafx.scene.text.Font?>
|
<?import javafx.scene.text.Font?>
|
||||||
|
<?import javafx.stage.Screen?>
|
||||||
|
|
||||||
<GridPane fx:id="scene" maxHeight="-Infinity"
|
<GridPane maxHeight="-Infinity" maxWidth="-Infinity"
|
||||||
maxWidth="-Infinity" minHeight="400.0" minWidth="500.0"
|
minHeight="400.0" minWidth="500.0"
|
||||||
prefHeight="1152.0" prefWidth="2042.0"
|
prefWidth="${screen.visualBounds.width}"
|
||||||
|
prefHeight="${screen.visualBounds.height}"
|
||||||
xmlns="http://javafx.com/javafx/11.0.1"
|
xmlns="http://javafx.com/javafx/11.0.1"
|
||||||
xmlns:fx="http://javafx.com/fxml/1"
|
xmlns:fx="http://javafx.com/fxml/1"
|
||||||
fx:controller="envoy.client.ui.controller.ChatScene">
|
fx:controller="envoy.client.ui.controller.ChatScene">
|
||||||
|
<fx:define>
|
||||||
|
<Screen fx:factory="getPrimary" fx:id="screen" />
|
||||||
|
</fx:define>
|
||||||
<columnConstraints>
|
<columnConstraints>
|
||||||
<ColumnConstraints hgrow="NEVER"
|
<ColumnConstraints hgrow="NEVER"
|
||||||
maxWidth="327.99997965494794" minWidth="-Infinity" prefWidth="317.0" />
|
maxWidth="327.99997965494794" minWidth="-Infinity" prefWidth="317.0" />
|
||||||
@ -52,8 +57,7 @@
|
|||||||
<content>
|
<content>
|
||||||
<AnchorPane minHeight="0.0" minWidth="0.0">
|
<AnchorPane minHeight="0.0" minWidth="0.0">
|
||||||
<children>
|
<children>
|
||||||
<VBox fx:id="contactOperations" prefHeight="3000.0"
|
<VBox prefHeight="3000.0" prefWidth="316.0">
|
||||||
prefWidth="316.0">
|
|
||||||
<children>
|
<children>
|
||||||
<VBox id="search-panel" maxHeight="-Infinity"
|
<VBox id="search-panel" maxHeight="-Infinity"
|
||||||
minHeight="-Infinity" prefHeight="80.0" prefWidth="316.0">
|
minHeight="-Infinity" prefHeight="80.0" prefWidth="316.0">
|
||||||
@ -121,15 +125,6 @@
|
|||||||
<ListView id="chat-list" fx:id="chatList"
|
<ListView id="chat-list" fx:id="chatList"
|
||||||
focusTraversable="false" onMouseClicked="#chatListClicked"
|
focusTraversable="false" onMouseClicked="#chatListClicked"
|
||||||
prefWidth="316.0" VBox.vgrow="ALWAYS">
|
prefWidth="316.0" VBox.vgrow="ALWAYS">
|
||||||
<contextMenu>
|
|
||||||
<ContextMenu anchorLocation="CONTENT_TOP_LEFT">
|
|
||||||
<items>
|
|
||||||
<MenuItem fx:id="deleteContactMenuItem"
|
|
||||||
mnemonicParsing="false" onAction="#deleteContact"
|
|
||||||
text="Delete" />
|
|
||||||
</items>
|
|
||||||
</ContextMenu>
|
|
||||||
</contextMenu>
|
|
||||||
<padding>
|
<padding>
|
||||||
<Insets bottom="5.0" left="5.0" right="2.0" top="5.0" />
|
<Insets bottom="5.0" left="5.0" right="2.0" top="5.0" />
|
||||||
</padding>
|
</padding>
|
||||||
@ -150,50 +145,27 @@
|
|||||||
<Insets right="1.0" />
|
<Insets right="1.0" />
|
||||||
</GridPane.margin>
|
</GridPane.margin>
|
||||||
</TabPane>
|
</TabPane>
|
||||||
<HBox id="top-bar" alignment="CENTER_LEFT" prefHeight="100.0">
|
<HBox id="top-bar" alignment="CENTER_LEFT" prefHeight="100.0"
|
||||||
|
fx:id="ownContactControl">
|
||||||
<children>
|
<children>
|
||||||
<ImageView id="profile-pic" fx:id="clientProfilePic"
|
<ImageView id="profile-pic" fx:id="clientProfilePic"
|
||||||
fitHeight="43.0" fitWidth="43.0" pickOnBounds="true"
|
fitHeight="43.0" fitWidth="43.0" pickOnBounds="true"
|
||||||
preserveRatio="true">
|
preserveRatio="true">
|
||||||
<HBox.margin>
|
<HBox.margin>
|
||||||
<Insets left="15.0" top="5.0" />
|
<Insets left="15.0" top="5.0" right="10.0" />
|
||||||
</HBox.margin>
|
</HBox.margin>
|
||||||
</ImageView>
|
</ImageView>
|
||||||
<Label id="transparent-background" fx:id="contactLabel"
|
<Region id="transparent-background" HBox.hgrow="ALWAYS" />
|
||||||
prefHeight="27.0" prefWidth="134.0">
|
<Button fx:id="settingsButton" mnemonicParsing="false"
|
||||||
<padding>
|
|
||||||
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
|
||||||
</padding>
|
|
||||||
<font>
|
|
||||||
<Font size="18.0" />
|
|
||||||
</font>
|
|
||||||
<HBox.margin>
|
|
||||||
<Insets left="10.0" top="5.0" />
|
|
||||||
</HBox.margin>
|
|
||||||
</Label>
|
|
||||||
<Region id="transparent-background" prefHeight="77.0"
|
|
||||||
prefWidth="115.0" />
|
|
||||||
<VBox id="transparent-background" alignment="CENTER_RIGHT"
|
|
||||||
prefHeight="200.0" prefWidth="100.0" spacing="5.0">
|
|
||||||
<children>
|
|
||||||
<Button fx:id="settingsButton" mnemonicParsing="true"
|
|
||||||
onAction="#settingsButtonClicked" prefHeight="30.0"
|
onAction="#settingsButtonClicked" prefHeight="30.0"
|
||||||
prefWidth="30.0" text="">
|
prefWidth="30.0" alignment="CENTER_RIGHT">
|
||||||
<padding>
|
<padding>
|
||||||
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||||
</padding>
|
</padding>
|
||||||
<VBox.margin>
|
|
||||||
<Insets />
|
|
||||||
</VBox.margin>
|
|
||||||
</Button>
|
|
||||||
</children>
|
|
||||||
<HBox.margin>
|
<HBox.margin>
|
||||||
<Insets right="10.0" />
|
<Insets bottom="35.0" left="5.0" top="35.0" right="10.0" />
|
||||||
</HBox.margin>
|
</HBox.margin>
|
||||||
<opaqueInsets>
|
</Button>
|
||||||
<Insets />
|
|
||||||
</opaqueInsets>
|
|
||||||
</VBox>
|
|
||||||
</children>
|
</children>
|
||||||
<GridPane.margin>
|
<GridPane.margin>
|
||||||
<Insets bottom="1.0" right="1.0" />
|
<Insets bottom="1.0" right="1.0" />
|
||||||
|
@ -64,6 +64,7 @@
|
|||||||
<Insets bottom="5.0" top="5" />
|
<Insets bottom="5.0" top="5" />
|
||||||
</VBox.margin>
|
</VBox.margin>
|
||||||
</Label>
|
</Label>
|
||||||
|
<ListView fx:id="quickSelectList" id="quick-select-list" orientation="HORIZONTAL" prefHeight="60.0" />
|
||||||
<ListView id="chat-list" fx:id="userList" focusTraversable="false" onMouseClicked="#userListClicked" prefWidth="316.0" VBox.vgrow="ALWAYS">
|
<ListView id="chat-list" fx:id="userList" focusTraversable="false" onMouseClicked="#userListClicked" prefWidth="316.0" VBox.vgrow="ALWAYS">
|
||||||
<contextMenu>
|
<contextMenu>
|
||||||
<ContextMenu anchorLocation="CONTENT_TOP_LEFT" />
|
<ContextMenu anchorLocation="CONTENT_TOP_LEFT" />
|
||||||
|
@ -7,11 +7,16 @@
|
|||||||
<?import javafx.scene.layout.HBox?>
|
<?import javafx.scene.layout.HBox?>
|
||||||
<?import javafx.scene.layout.VBox?>
|
<?import javafx.scene.layout.VBox?>
|
||||||
|
|
||||||
<VBox alignment="TOP_RIGHT" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="envoy.client.ui.controller.SettingsScene">
|
<VBox alignment="TOP_RIGHT" maxHeight="-Infinity" minHeight="400.0"
|
||||||
|
minWidth="500.0" xmlns="http://javafx.com/javafx/11.0.1"
|
||||||
|
xmlns:fx="http://javafx.com/fxml/1"
|
||||||
|
fx:controller="envoy.client.ui.controller.SettingsScene">
|
||||||
<children>
|
<children>
|
||||||
<HBox prefHeight="389.0" prefWidth="600.0">
|
<HBox prefHeight="389.0" prefWidth="600.0">
|
||||||
<children>
|
<children>
|
||||||
<ListView id="message-list" fx:id="settingsList" onMouseClicked="#settingsListClicked" prefHeight="200.0" prefWidth="200.0">
|
<ListView id="message-list" fx:id="settingsList"
|
||||||
|
onMouseClicked="#settingsListClicked" prefHeight="200.0"
|
||||||
|
prefWidth="200.0">
|
||||||
<opaqueInsets>
|
<opaqueInsets>
|
||||||
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||||
</opaqueInsets>
|
</opaqueInsets>
|
||||||
@ -22,7 +27,8 @@
|
|||||||
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||||
</padding>
|
</padding>
|
||||||
</ListView>
|
</ListView>
|
||||||
<TitledPane fx:id="titledPane" collapsible="false" prefHeight="400.0" prefWidth="400.0">
|
<TitledPane fx:id="titledPane" collapsible="false"
|
||||||
|
prefHeight="400.0" prefWidth="400.0">
|
||||||
<HBox.margin>
|
<HBox.margin>
|
||||||
<Insets bottom="10.0" left="5.0" right="10.0" top="10.0" />
|
<Insets bottom="10.0" left="5.0" right="10.0" top="10.0" />
|
||||||
</HBox.margin>
|
</HBox.margin>
|
||||||
@ -32,7 +38,8 @@
|
|||||||
</TitledPane>
|
</TitledPane>
|
||||||
</children>
|
</children>
|
||||||
</HBox>
|
</HBox>
|
||||||
<Button defaultButton="true" mnemonicParsing="true" onMouseClicked="#backButtonClicked" text="_Back">
|
<Button defaultButton="true" mnemonicParsing="true"
|
||||||
|
onMouseClicked="#backButtonClicked" text="_Back">
|
||||||
<opaqueInsets>
|
<opaqueInsets>
|
||||||
<Insets />
|
<Insets />
|
||||||
</opaqueInsets>
|
</opaqueInsets>
|
||||||
|
@ -128,364 +128,4 @@ org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
|
|||||||
org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
|
org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
|
||||||
org.eclipse.jdt.core.compiler.release=disabled
|
org.eclipse.jdt.core.compiler.release=disabled
|
||||||
org.eclipse.jdt.core.compiler.source=11
|
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=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_if_empty
|
|
||||||
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_if_empty
|
|
||||||
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
|
org.eclipse.jdt.core.javaFormatter=org.eclipse.jdt.core.defaultJavaFormatter
|
||||||
|
File diff suppressed because one or more lines are too long
@ -12,18 +12,11 @@
|
|||||||
<version>0.2-beta</version>
|
<version>0.2-beta</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<repositories>
|
|
||||||
<repository>
|
|
||||||
<id>kske-repo</id>
|
|
||||||
<url>https://kske.dev/maven-repo</url>
|
|
||||||
</repository>
|
|
||||||
</repositories>
|
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>dev.kske</groupId>
|
<groupId>dev.kske</groupId>
|
||||||
<artifactId>event-bus</artifactId>
|
<artifactId>event-bus-core</artifactId>
|
||||||
<version>0.0.4</version>
|
<version>1.0.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.junit.jupiter</groupId>
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
package envoy.data;
|
package envoy.data;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This interface should be used for any type supposed to be a {@link Message}
|
* This interface should be used for any type supposed to be a {@link Message} attachment (i.e.
|
||||||
* attachment (i.e. images or sound).
|
* images or sound).
|
||||||
*
|
*
|
||||||
* @author Leon Hofmeister
|
* @author Leon Hofmeister
|
||||||
* @author Kai S. K. Engelbart
|
* @author Kai S. K. Engelbart
|
||||||
@ -63,9 +64,9 @@ public final class Attachment implements Serializable {
|
|||||||
* @since Envoy Common v0.1-beta
|
* @since Envoy Common v0.1-beta
|
||||||
*/
|
*/
|
||||||
public Attachment(byte[] data, String name, AttachmentType type) {
|
public Attachment(byte[] data, String name, AttachmentType type) {
|
||||||
this.data = data;
|
this.data = Objects.requireNonNull(data);
|
||||||
this.name = name;
|
this.name = Objects.requireNonNull(name);
|
||||||
this.type = type;
|
this.type = Objects.requireNonNull(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -9,15 +9,13 @@ import java.util.stream.Collectors;
|
|||||||
import envoy.util.EnvoyLog;
|
import envoy.util.EnvoyLog;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages all application settings that are set during application startup by
|
* Manages all application settings that are set during application startup by either loading them
|
||||||
* either loading them from the {@link Properties} file (default values)
|
* from the {@link Properties} file (default values) {@code client.properties} or parsing them from
|
||||||
* {@code client.properties} or parsing them from the command line arguments of
|
* the command line arguments of the application.
|
||||||
* the application.
|
|
||||||
* <p>
|
* <p>
|
||||||
* All items inside the {@code Config} are supposed to either be supplied over
|
* All items inside the {@code Config} are supposed to either be supplied over default value or over
|
||||||
* default value or over command line argument. Developers that fail to provide
|
* command line argument. Developers that fail to provide default values will be greeted with an
|
||||||
* default values will be greeted with an error message the next time they try
|
* error message the next time they try to start Envoy...
|
||||||
* to start Envoy...
|
|
||||||
*
|
*
|
||||||
* @author Kai S. K. Engelbart
|
* @author Kai S. K. Engelbart
|
||||||
* @since Envoy Common v0.1-beta
|
* @since Envoy Common v0.1-beta
|
||||||
@ -51,8 +49,7 @@ public class Config {
|
|||||||
* Parses config items from an array of command line arguments.
|
* Parses config items from an array of command line arguments.
|
||||||
*
|
*
|
||||||
* @param args the command line arguments to parse
|
* @param args the command line arguments to parse
|
||||||
* @throws IllegalStateException if a malformed command line argument has been
|
* @throws IllegalStateException if a malformed command line argument has been supplied
|
||||||
* supplied
|
|
||||||
* @since Envoy Common v0.1-beta
|
* @since Envoy Common v0.1-beta
|
||||||
*/
|
*/
|
||||||
private void load(String[] args) {
|
private void load(String[] args) {
|
||||||
@ -82,30 +79,31 @@ public class Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Supplies default values from the given .properties file and parses the
|
* Supplies default values from the given .properties file and parses the configuration from an
|
||||||
* configuration from an array of command line arguments.
|
* array of command line arguments.
|
||||||
*
|
*
|
||||||
* @param declaringClass the class calling this method
|
* @param declaringClass the class calling this method
|
||||||
* @param propertiesFilePath the path to where the .properties file can be found
|
* @param propertiesFilePath the path to where the .properties file can be found - will be only
|
||||||
* - will be only the file name if it is located
|
* the file name if it is located directly inside the
|
||||||
* directly inside the {@code src/main/resources}
|
* {@code src/main/resources} folder
|
||||||
* folder
|
|
||||||
* @param args the command line arguments to parse
|
* @param args the command line arguments to parse
|
||||||
* @throws IllegalStateException if this method is getting called again or if a
|
* @throws IllegalStateException if this method is getting called again or if a malformed
|
||||||
* malformed command line argument has been
|
* command line argument has been supplied
|
||||||
* supplied
|
|
||||||
* @since Envoy Common v0.1-beta
|
* @since Envoy Common v0.1-beta
|
||||||
*/
|
*/
|
||||||
public void loadAll(Class<?> declaringClass, String propertiesFilePath, String[] args) {
|
public void loadAll(Class<?> declaringClass, String propertiesFilePath, String[] args) {
|
||||||
if (modificationDisabled)
|
if (modificationDisabled)
|
||||||
throw new IllegalStateException("Cannot change config after isInitialized has been called");
|
throw new IllegalStateException(
|
||||||
|
"Cannot change config after isInitialized has been called");
|
||||||
|
|
||||||
// Load the defaults from the given .properties file first
|
// Load the defaults from the given .properties file first
|
||||||
final var properties = new Properties();
|
final var properties = new Properties();
|
||||||
try {
|
try {
|
||||||
properties.load(declaringClass.getClassLoader().getResourceAsStream(propertiesFilePath));
|
properties
|
||||||
|
.load(declaringClass.getClassLoader().getResourceAsStream(propertiesFilePath));
|
||||||
} catch (final IOException e) {
|
} catch (final IOException e) {
|
||||||
EnvoyLog.getLogger(Config.class).log(Level.SEVERE, "An error occurred when reading in the configuration: ",
|
EnvoyLog.getLogger(Config.class).log(Level.SEVERE,
|
||||||
|
"An error occurred when reading in the configuration: ",
|
||||||
e);
|
e);
|
||||||
}
|
}
|
||||||
load(properties);
|
load(properties);
|
||||||
@ -122,13 +120,13 @@ public class Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws IllegalStateException if a {@link ConfigItem} has not been
|
* @throws IllegalStateException if a {@link ConfigItem} has not been initialized
|
||||||
* initialized
|
|
||||||
* @since Envoy Common v0.1-beta
|
* @since Envoy Common v0.1-beta
|
||||||
*/
|
*/
|
||||||
private void isInitialized() {
|
private void isInitialized() {
|
||||||
String uninitialized = items.values().stream().filter(c -> c.get() == null).map(ConfigItem::getCommandLong).collect(Collectors.joining(", "));
|
String uninitialized = items.values().stream().filter(c -> c.get() == null)
|
||||||
if(!uninitialized.isEmpty())
|
.map(ConfigItem::getCommandLong).collect(Collectors.joining(", "));
|
||||||
|
if (!uninitialized.isEmpty())
|
||||||
throw new IllegalStateException("Config items uninitialized: " + uninitialized);
|
throw new IllegalStateException("Config items uninitialized: " + uninitialized);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,11 +146,11 @@ public class Config {
|
|||||||
* @param <T> the type of the {@link ConfigItem}
|
* @param <T> the type of the {@link ConfigItem}
|
||||||
* @param commandName the key for this config item as well as its long name
|
* @param commandName the key for this config item as well as its long name
|
||||||
* @param commandShort the abbreviation of this config item
|
* @param commandShort the abbreviation of this config item
|
||||||
* @param parseFunction the {@code Function<String, T>} that parses the value
|
* @param parseFunction the {@code Function<String, T>} that parses the value from a string
|
||||||
* from a string
|
|
||||||
* @since Envoy Common v0.2-beta
|
* @since Envoy Common v0.2-beta
|
||||||
*/
|
*/
|
||||||
protected <T> void put(String commandName, String commandShort, Function<String, T> parseFunction) {
|
protected <T> void put(String commandName, String commandShort,
|
||||||
|
Function<String, T> parseFunction) {
|
||||||
items.put(commandName, new ConfigItem<>(commandName, commandShort, parseFunction));
|
items.put(commandName, new ConfigItem<>(commandName, commandShort, parseFunction));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,17 +158,13 @@ public class Config {
|
|||||||
* @return the directory in which all local files are saves
|
* @return the directory in which all local files are saves
|
||||||
* @since Envoy Client v0.2-beta
|
* @since Envoy Client v0.2-beta
|
||||||
*/
|
*/
|
||||||
public File getHomeDirectory() {
|
public File getHomeDirectory() { return (File) items.get("homeDirectory").get(); }
|
||||||
return (File) items.get("homeDirectory").get();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the minimal {@link Level} to log inside the log file
|
* @return the minimal {@link Level} to log inside the log file
|
||||||
* @since Envoy Client v0.2-beta
|
* @since Envoy Client v0.2-beta
|
||||||
*/
|
*/
|
||||||
public Level getFileLevelBarrier() {
|
public Level getFileLevelBarrier() { return (Level) items.get("fileLevelBarrier").get(); }
|
||||||
return (Level) items.get("fileLevelBarrier").get();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the minimal {@link Level} to log inside the console
|
* @return the minimal {@link Level} to log inside the console
|
||||||
|
@ -3,8 +3,8 @@ package envoy.data;
|
|||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contains a single {@link Config} value as well as the corresponding command
|
* Contains a single {@link Config} value as well as the corresponding command line arguments and
|
||||||
* line arguments and its default value.
|
* its default value.
|
||||||
* <p>
|
* <p>
|
||||||
* All {@code ConfigItem}s are automatically mandatory.
|
* All {@code ConfigItem}s are automatically mandatory.
|
||||||
*
|
*
|
||||||
@ -24,8 +24,7 @@ public final class ConfigItem<T> {
|
|||||||
*
|
*
|
||||||
* @param commandLong the long command line argument to set this value
|
* @param commandLong the long command line argument to set this value
|
||||||
* @param commandShort the short command line argument to set this value
|
* @param commandShort the short command line argument to set this value
|
||||||
* @param parseFunction the {@code Function<String, T>} that parses the value
|
* @param parseFunction the {@code Function<String, T>} that parses the value from a string
|
||||||
* from a string
|
|
||||||
* @since Envoy Common v0.1-beta
|
* @since Envoy Common v0.1-beta
|
||||||
*/
|
*/
|
||||||
public ConfigItem(String commandLong, String commandShort, Function<String, T> parseFunction) {
|
public ConfigItem(String commandLong, String commandShort, Function<String, T> parseFunction) {
|
||||||
@ -40,18 +39,18 @@ public final class ConfigItem<T> {
|
|||||||
* @param input the string to parse from
|
* @param input the string to parse from
|
||||||
* @since Envoy Common v0.1-beta
|
* @since Envoy Common v0.1-beta
|
||||||
*/
|
*/
|
||||||
public void parse(String input) { value = parseFunction.apply(input); }
|
public void parse(String input) {
|
||||||
|
value = parseFunction.apply(input);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The long command line argument to set the value of this
|
* @return The long command line argument to set the value of this {@link ConfigItem}
|
||||||
* {@link ConfigItem}
|
|
||||||
* @since Envoy Common v0.1-beta
|
* @since Envoy Common v0.1-beta
|
||||||
*/
|
*/
|
||||||
public String getCommandLong() { return commandLong; }
|
public String getCommandLong() { return commandLong; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The short command line argument to set the value of this
|
* @return The short command line argument to set the value of this {@link ConfigItem}
|
||||||
* {@link ConfigItem}
|
|
||||||
* @since Envoy Common v0.1-beta
|
* @since Envoy Common v0.1-beta
|
||||||
*/
|
*/
|
||||||
public String getCommandShort() { return commandShort; }
|
public String getCommandShort() { return commandShort; }
|
||||||
@ -60,7 +59,9 @@ public final class ConfigItem<T> {
|
|||||||
* @return the value of this {@link ConfigItem}
|
* @return the value of this {@link ConfigItem}
|
||||||
* @since Envoy Common v0.1-beta
|
* @since Envoy Common v0.1-beta
|
||||||
*/
|
*/
|
||||||
public T get() { return value; }
|
public T get() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param value the value to set
|
* @param value the value to set
|
||||||
|
@ -29,8 +29,8 @@ public abstract class Contact implements Serializable {
|
|||||||
*/
|
*/
|
||||||
public Contact(long id, String name, Set<? extends Contact> contacts) {
|
public Contact(long id, String name, Set<? extends Contact> contacts) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.name = name;
|
this.name = Objects.requireNonNull(name);
|
||||||
this.contacts = contacts;
|
this.contacts = contacts == null ? new HashSet<>() : contacts;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -57,19 +57,23 @@ public abstract class Contact implements Serializable {
|
|||||||
* @since Envoy Common v0.1-beta
|
* @since Envoy Common v0.1-beta
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public final int hashCode() { return Objects.hash(id); }
|
public final int hashCode() {
|
||||||
|
return Objects.hash(id);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests equality to another object. If that object is a contact as well,
|
* Tests equality to another object. If that object is a contact as well, equality is determined
|
||||||
* equality is determined by the ID.
|
* by the ID.
|
||||||
*
|
*
|
||||||
* @param obj the object to test for equality to this contact
|
* @param obj the object to test for equality to this contact
|
||||||
* @return {code true} if both objects are contacts and have identical IDs
|
* @return {code true} if both objects are contacts and have identical IDs
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public final boolean equals(Object obj) {
|
public final boolean equals(Object obj) {
|
||||||
if (this == obj) return true;
|
if (this == obj)
|
||||||
if (!(obj instanceof Contact)) return false;
|
return true;
|
||||||
|
if (!(obj instanceof Contact))
|
||||||
|
return false;
|
||||||
return id == ((Contact) obj).id;
|
return id == ((Contact) obj).id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,7 +18,9 @@ public final class Group extends Contact {
|
|||||||
* @param name the name of this group
|
* @param name the name of this group
|
||||||
* @since Envoy Common v0.1-beta
|
* @since Envoy Common v0.1-beta
|
||||||
*/
|
*/
|
||||||
public Group(long id, String name) { this(id, name, new HashSet<User>()); }
|
public Group(long id, String name) {
|
||||||
|
this(id, name, new HashSet<User>());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an instance of a {@link Group}.
|
* Creates an instance of a {@link Group}.
|
||||||
@ -28,10 +30,14 @@ public final class Group extends Contact {
|
|||||||
* @param members all members that should be preinitialized
|
* @param members all members that should be preinitialized
|
||||||
* @since Envoy Common v0.1-beta
|
* @since Envoy Common v0.1-beta
|
||||||
*/
|
*/
|
||||||
public Group(long id, String name, Set<User> members) { super(id, name, members); }
|
public Group(long id, String name, Set<User> members) {
|
||||||
|
super(id, name, members);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() { return String.format("Group[id=%d,name=%s,%d member(s)]", id, name, contacts.size()); }
|
public String toString() {
|
||||||
|
return String.format("Group[id=%d,name=%s,%d member(s)]", id, name, contacts.size());
|
||||||
|
}
|
||||||
|
|
||||||
private void readObject(ObjectInputStream inputStream) throws Exception {
|
private void readObject(ObjectInputStream inputStream) throws Exception {
|
||||||
inputStream.defaultReadObject();
|
inputStream.defaultReadObject();
|
||||||
|
@ -14,11 +14,9 @@ public final class GroupMessage extends Message {
|
|||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes a {@link GroupMessage} with values for all of its properties. The
|
* Initializes a {@link GroupMessage} with values for all of its properties. The use of this
|
||||||
* use
|
* constructor is only intended for the {@link MessageBuilder} class, as this class provides
|
||||||
* of this constructor is only intended for the {@link MessageBuilder} class, as
|
* {@code null} checks and default values for all properties.
|
||||||
* this class provides {@code null} checks and default values for all
|
|
||||||
* properties.
|
|
||||||
*
|
*
|
||||||
* @param id unique ID
|
* @param id unique ID
|
||||||
* @param senderID the ID of the user who sends the message
|
* @param senderID the ID of the user who sends the message
|
||||||
@ -28,17 +26,20 @@ public final class GroupMessage extends Message {
|
|||||||
* @param readDate the read date of the message
|
* @param readDate the read date of the message
|
||||||
* @param text the text content of the message
|
* @param text the text content of the message
|
||||||
* @param attachment the attachment of the message, if present
|
* @param attachment the attachment of the message, if present
|
||||||
* @param status the current {@link Message.MessageStatus} of the
|
* @param status the current {@link Message.MessageStatus} of the message
|
||||||
* message
|
|
||||||
* @param forwarded whether this message was forwarded
|
* @param forwarded whether this message was forwarded
|
||||||
* @param memberStatuses a map of all members and their status according to this
|
* @param memberStatuses a map of all members and their status according to this
|
||||||
* {@link GroupMessage}
|
* {@link GroupMessage}
|
||||||
* @since Envoy Common v0.2-beta
|
* @since Envoy Common v0.2-beta
|
||||||
*/
|
*/
|
||||||
GroupMessage(long id, long senderID, long groupID, Instant creationDate, Instant receivedDate, Instant readDate, String text,
|
GroupMessage(long id, long senderID, long groupID, Instant creationDate, Instant receivedDate,
|
||||||
Attachment attachment, MessageStatus status, boolean forwarded, Map<Long, MessageStatus> memberStatuses) {
|
Instant readDate, String text,
|
||||||
super(id, senderID, groupID, creationDate, receivedDate, readDate, text, attachment, status, forwarded);
|
Attachment attachment, MessageStatus status, boolean forwarded,
|
||||||
this.memberStatuses = memberStatuses;
|
Map<Long, MessageStatus> memberStatuses) {
|
||||||
|
super(id, senderID, groupID, creationDate, receivedDate, readDate, text, attachment, status,
|
||||||
|
forwarded);
|
||||||
|
this.memberStatuses =
|
||||||
|
memberStatuses == null ? new HashMap<>() : memberStatuses;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2,15 +2,13 @@ package envoy.data;
|
|||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
||||||
import dev.kske.eventbus.IEvent;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates increasing IDs between two numbers.
|
* Generates increasing IDs between two numbers.
|
||||||
*
|
*
|
||||||
* @author Kai S. K. Engelbart
|
* @author Kai S. K. Engelbart
|
||||||
* @since Envoy Common v0.2-alpha
|
* @since Envoy Common v0.2-alpha
|
||||||
*/
|
*/
|
||||||
public final class IDGenerator implements IEvent, Serializable {
|
public final class IDGenerator implements Serializable {
|
||||||
|
|
||||||
private final long end;
|
private final long end;
|
||||||
private long current;
|
private long current;
|
||||||
@ -30,20 +28,25 @@ public final class IDGenerator implements IEvent, Serializable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() { return String.format("IDGenerator[current=%d,end=%d]", current, end); }
|
public String toString() {
|
||||||
|
return String.format("IDGenerator[current=%d,end=%d]", current, end);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return {@code true} if there are unused IDs remaining
|
* @return {@code true} if there are unused IDs remaining
|
||||||
* @since Envoy Common v0.2-alpha
|
* @since Envoy Common v0.2-alpha
|
||||||
*/
|
*/
|
||||||
public boolean hasNext() { return current < end; }
|
public boolean hasNext() {
|
||||||
|
return current < end;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the next ID
|
* @return the next ID
|
||||||
* @since Envoy Common v0.2-alpha
|
* @since Envoy Common v0.2-alpha
|
||||||
*/
|
*/
|
||||||
public long next() {
|
public long next() {
|
||||||
if (!hasNext()) throw new IllegalStateException("All IDs have been used");
|
if (!hasNext())
|
||||||
|
throw new IllegalStateException("All IDs have been used");
|
||||||
return current++;
|
return current++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,13 +2,12 @@ package envoy.data;
|
|||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contains a {@link User}'s login / registration information as well as the
|
* Contains a {@link User}'s login / registration information as well as the client version.
|
||||||
* client version.
|
|
||||||
* <p>
|
* <p>
|
||||||
* If the authentication is performed with a token, the token is stored instead
|
* If the authentication is performed with a token, the token is stored instead of the password.
|
||||||
* of the password.
|
|
||||||
*
|
*
|
||||||
* @author Kai S. K. Engelbart
|
* @author Kai S. K. Engelbart
|
||||||
* @since Envoy Common v0.2-alpha
|
* @since Envoy Common v0.2-alpha
|
||||||
@ -21,15 +20,15 @@ public final class LoginCredentials implements Serializable {
|
|||||||
|
|
||||||
private static final long serialVersionUID = 4;
|
private static final long serialVersionUID = 4;
|
||||||
|
|
||||||
private LoginCredentials(String identifier, String password, boolean registration, boolean token, boolean requestToken, String clientVersion,
|
private LoginCredentials(String identifier, String password, boolean registration,
|
||||||
Instant lastSync) {
|
boolean token, boolean requestToken, String clientVersion, Instant lastSync) {
|
||||||
this.identifier = identifier;
|
this.identifier = Objects.requireNonNull(identifier);
|
||||||
this.password = password;
|
this.password = Objects.requireNonNull(password);
|
||||||
this.registration = registration;
|
this.registration = registration;
|
||||||
this.token = token;
|
this.token = token;
|
||||||
this.requestToken = requestToken;
|
this.requestToken = requestToken;
|
||||||
this.clientVersion = clientVersion;
|
this.clientVersion = Objects.requireNonNull(clientVersion);
|
||||||
this.lastSync = lastSync;
|
this.lastSync = lastSync == null ? Instant.EPOCH : lastSync;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -43,8 +42,10 @@ public final class LoginCredentials implements Serializable {
|
|||||||
* @return the created login credentials
|
* @return the created login credentials
|
||||||
* @since Envoy Common v0.2-beta
|
* @since Envoy Common v0.2-beta
|
||||||
*/
|
*/
|
||||||
public static LoginCredentials login(String identifier, String password, boolean requestToken, String clientVersion, Instant lastSync) {
|
public static LoginCredentials login(String identifier, String password, boolean requestToken,
|
||||||
return new LoginCredentials(identifier, password, false, false, requestToken, clientVersion, lastSync);
|
String clientVersion, Instant lastSync) {
|
||||||
|
return new LoginCredentials(identifier, password, false, false, requestToken, clientVersion,
|
||||||
|
lastSync);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -57,7 +58,8 @@ public final class LoginCredentials implements Serializable {
|
|||||||
* @return the created login credentials
|
* @return the created login credentials
|
||||||
* @since Envoy Common v0.2-beta
|
* @since Envoy Common v0.2-beta
|
||||||
*/
|
*/
|
||||||
public static LoginCredentials loginWithToken(String identifier, String token, String clientVersion, Instant lastSync) {
|
public static LoginCredentials loginWithToken(String identifier, String token,
|
||||||
|
String clientVersion, Instant lastSync) {
|
||||||
return new LoginCredentials(identifier, token, false, true, false, clientVersion, lastSync);
|
return new LoginCredentials(identifier, token, false, true, false, clientVersion, lastSync);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,13 +74,17 @@ public final class LoginCredentials implements Serializable {
|
|||||||
* @return the created login credentials
|
* @return the created login credentials
|
||||||
* @since Envoy Common v0.2-beta
|
* @since Envoy Common v0.2-beta
|
||||||
*/
|
*/
|
||||||
public static LoginCredentials registration(String identifier, String password, boolean requestToken, String clientVersion, Instant lastSync) {
|
public static LoginCredentials registration(String identifier, String password,
|
||||||
return new LoginCredentials(identifier, password, true, false, requestToken, clientVersion, lastSync);
|
boolean requestToken,
|
||||||
|
String clientVersion, Instant lastSync) {
|
||||||
|
return new LoginCredentials(identifier, password, true, false, requestToken, clientVersion,
|
||||||
|
lastSync);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return String.format("LoginCredentials[identifier=%s,registration=%b,token=%b,requestToken=%b,clientVersion=%s,lastSync=%s]",
|
return String.format(
|
||||||
|
"LoginCredentials[identifier=%s,registration=%b,token=%b,requestToken=%b,clientVersion=%s,lastSync=%s]",
|
||||||
identifier,
|
identifier,
|
||||||
registration,
|
registration,
|
||||||
token,
|
token,
|
||||||
@ -100,24 +106,27 @@ public final class LoginCredentials implements Serializable {
|
|||||||
public String getPassword() { return password; }
|
public String getPassword() { return password; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return {@code true} if these credentials are used for user registration
|
* @return {@code true} if these credentials are used for user registration instead of user
|
||||||
* instead of user login
|
* login
|
||||||
* @since Envoy Common v0.2-alpha
|
* @since Envoy Common v0.2-alpha
|
||||||
*/
|
*/
|
||||||
public boolean isRegistration() { return registration; }
|
public boolean isRegistration() { return registration; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return {@code true} if these credentials use an authentication token instead
|
* @return {@code true} if these credentials use an authentication token instead of a password
|
||||||
* of a password
|
|
||||||
* @since Envoy Common v0.2-beta
|
* @since Envoy Common v0.2-beta
|
||||||
*/
|
*/
|
||||||
public boolean usesToken() { return token; }
|
public boolean usesToken() {
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return {@code true} if the server should generate a new authentication token
|
* @return {@code true} if the server should generate a new authentication token
|
||||||
* @since Envoy Common v0.2-beta
|
* @since Envoy Common v0.2-beta
|
||||||
*/
|
*/
|
||||||
public boolean requestToken() { return requestToken; }
|
public boolean requestToken() {
|
||||||
|
return requestToken;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the version of the client sending these credentials
|
* @return the version of the client sending these credentials
|
||||||
|
@ -2,19 +2,17 @@ package envoy.data;
|
|||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
import java.util.Objects;
|
||||||
import dev.kske.eventbus.IEvent;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a unique message with a unique, numeric ID. Further metadata
|
* Represents a unique message with a unique, numeric ID. Further metadata includes the sender and
|
||||||
* includes the sender and recipient {@link User}s, as well as the creation
|
* recipient {@link User}s, as well as the creation date and the current {@link MessageStatus}.<br>
|
||||||
* date and the current {@link MessageStatus}.<br>
|
|
||||||
*
|
*
|
||||||
* @author Kai S. K. Engelbart
|
* @author Kai S. K. Engelbart
|
||||||
* @author Leon Hofmeister
|
* @author Leon Hofmeister
|
||||||
* @since Envoy Common v0.2-alpha
|
* @since Envoy Common v0.2-alpha
|
||||||
*/
|
*/
|
||||||
public class Message implements Serializable, IEvent {
|
public class Message implements Serializable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This enumeration defines all possible statuses a {link Message} can have.
|
* This enumeration defines all possible statuses a {link Message} can have.
|
||||||
@ -56,10 +54,9 @@ public class Message implements Serializable, IEvent {
|
|||||||
private static final long serialVersionUID = 2L;
|
private static final long serialVersionUID = 2L;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes a {@link Message} with values for all of its properties. The use
|
* Initializes a {@link Message} with values for all of its properties. The use of this
|
||||||
* of this constructor is only intended for the {@link MessageBuilder} class, as
|
* constructor is only intended for the {@link MessageBuilder} class, as this class provides
|
||||||
* this class provides {@code null} checks and default values for all
|
* {@code null} checks and default values for all properties.
|
||||||
* properties.
|
|
||||||
*
|
*
|
||||||
* @param id unique ID
|
* @param id unique ID
|
||||||
* @param senderID the ID of the user who sends the message
|
* @param senderID the ID of the user who sends the message
|
||||||
@ -73,7 +70,8 @@ public class Message implements Serializable, IEvent {
|
|||||||
* @param forwarded whether this message was forwarded
|
* @param forwarded whether this message was forwarded
|
||||||
* @since Envoy Common v0.2-beta
|
* @since Envoy Common v0.2-beta
|
||||||
*/
|
*/
|
||||||
Message(long id, long senderID, long recipientID, Instant creationDate, Instant receivedDate, Instant readDate, String text,
|
Message(long id, long senderID, long recipientID, Instant creationDate, Instant receivedDate,
|
||||||
|
Instant readDate, String text,
|
||||||
Attachment attachment, MessageStatus status, boolean forwarded) {
|
Attachment attachment, MessageStatus status, boolean forwarded) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.senderID = senderID;
|
this.senderID = senderID;
|
||||||
@ -81,9 +79,9 @@ public class Message implements Serializable, IEvent {
|
|||||||
this.creationDate = creationDate;
|
this.creationDate = creationDate;
|
||||||
this.receivedDate = receivedDate;
|
this.receivedDate = receivedDate;
|
||||||
this.readDate = readDate;
|
this.readDate = readDate;
|
||||||
this.text = text;
|
this.text = text == null ? "" : text;
|
||||||
this.attachment = attachment;
|
this.attachment = attachment;
|
||||||
this.status = status;
|
this.status = Objects.requireNonNull(status);
|
||||||
this.forwarded = forwarded;
|
this.forwarded = forwarded;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,13 +99,15 @@ public class Message implements Serializable, IEvent {
|
|||||||
* @since Envoy Common v0.2-alpha
|
* @since Envoy Common v0.2-alpha
|
||||||
*/
|
*/
|
||||||
public void nextStatus() {
|
public void nextStatus() {
|
||||||
if (status == MessageStatus.READ) throw new IllegalStateException("Message status READ is already reached");
|
if (status == MessageStatus.READ)
|
||||||
|
throw new IllegalStateException("Message status READ is already reached");
|
||||||
status = MessageStatus.values()[status.ordinal() + 1];
|
status = MessageStatus.values()[status.ordinal() + 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return String.format("Message[id=%d,sender=%s,recipient=%s,date=%s,status=%s,text=%s,forwarded=%b,hasAttachment=%b]",
|
return String.format(
|
||||||
|
"Message[id=%d,sender=%s,recipient=%s,date=%s,status=%s,text=%s,forwarded=%b,hasAttachment=%b]",
|
||||||
id,
|
id,
|
||||||
senderID,
|
senderID,
|
||||||
recipientID,
|
recipientID,
|
||||||
@ -149,8 +149,7 @@ public class Message implements Serializable, IEvent {
|
|||||||
public Instant getReceivedDate() { return receivedDate; }
|
public Instant getReceivedDate() { return receivedDate; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param receivedDate the date at which the message has been received by the
|
* @param receivedDate the date at which the message has been received by the sender
|
||||||
* sender
|
|
||||||
* @since Envoy Common v0.2-beta
|
* @since Envoy Common v0.2-beta
|
||||||
*/
|
*/
|
||||||
public void setReceivedDate(Instant receivedDate) { this.receivedDate = receivedDate; }
|
public void setReceivedDate(Instant receivedDate) { this.receivedDate = receivedDate; }
|
||||||
@ -183,7 +182,9 @@ public class Message implements Serializable, IEvent {
|
|||||||
* @return {@code true} if an attachment is present
|
* @return {@code true} if an attachment is present
|
||||||
* @since Envoy Common v0.1-beta
|
* @since Envoy Common v0.1-beta
|
||||||
*/
|
*/
|
||||||
public boolean hasAttachment() { return attachment != null; }
|
public boolean hasAttachment() {
|
||||||
|
return attachment != null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the current status of this message
|
* @return the current status of this message
|
||||||
@ -196,7 +197,8 @@ public class Message implements Serializable, IEvent {
|
|||||||
* @since Envoy Common v0.2-alpha
|
* @since Envoy Common v0.2-alpha
|
||||||
*/
|
*/
|
||||||
public void setStatus(MessageStatus status) {
|
public void setStatus(MessageStatus status) {
|
||||||
if (status.ordinal() < this.status.ordinal()) throw new IllegalStateException("This message is moving backwards in time");
|
if (status.ordinal() < this.status.ordinal())
|
||||||
|
throw new IllegalStateException("This message is moving backwards in time");
|
||||||
this.status = status;
|
this.status = status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,20 +25,21 @@ public final class MessageBuilder {
|
|||||||
private boolean forwarded;
|
private boolean forwarded;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an instance of {@link MessageBuilder} with all mandatory values
|
* Creates an instance of {@link MessageBuilder} with all mandatory values without defaults for
|
||||||
* without defaults for the {@link Message} class.
|
* the {@link Message} class.
|
||||||
*
|
*
|
||||||
* @param senderID the ID of the user who sends the {@link Message}
|
* @param senderID the ID of the user who sends the {@link Message}
|
||||||
* @param recipientID the ID of the user who receives the {@link Message}
|
* @param recipientID the ID of the user who receives the {@link Message}
|
||||||
* @param idGenerator the ID generator used to generate a unique {@link Message}
|
* @param idGenerator the ID generator used to generate a unique {@link Message} id
|
||||||
* id
|
|
||||||
* @since Envoy Common v0.2-alpha
|
* @since Envoy Common v0.2-alpha
|
||||||
*/
|
*/
|
||||||
public MessageBuilder(long senderID, long recipientID, IDGenerator idGenerator) { this(senderID, recipientID, idGenerator.next()); }
|
public MessageBuilder(long senderID, long recipientID, IDGenerator idGenerator) {
|
||||||
|
this(senderID, recipientID, idGenerator.next());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an instance of {@link MessageBuilder} with all mandatory values
|
* Creates an instance of {@link MessageBuilder} with all mandatory values without defaults for
|
||||||
* without defaults for the {@link Message} class.
|
* the {@link Message} class.
|
||||||
*
|
*
|
||||||
* @param senderID the ID of the user who sends the {@link Message}
|
* @param senderID the ID of the user who sends the {@link Message}
|
||||||
* @param recipientID the ID of the user who receives the {@link Message}
|
* @param recipientID the ID of the user who receives the {@link Message}
|
||||||
@ -52,14 +53,12 @@ public final class MessageBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This constructor transforms a given {@link Message} into a new message for a
|
* This constructor transforms a given {@link Message} into a new message for a new receiver.
|
||||||
* new receiver.
|
|
||||||
* This makes it especially useful in the case of forwarding messages.
|
* This makes it especially useful in the case of forwarding messages.
|
||||||
*
|
*
|
||||||
* @param msg the message to copy
|
* @param msg the message to copy
|
||||||
* @param recipientID the ID of the user who receives the {@link Message}
|
* @param recipientID the ID of the user who receives the {@link Message}
|
||||||
* @param iDGenerator the ID generator used to generate a unique {@link Message}
|
* @param iDGenerator the ID generator used to generate a unique {@link Message} id
|
||||||
* id
|
|
||||||
* @since Envoy v0.1-beta
|
* @since Envoy v0.1-beta
|
||||||
*/
|
*/
|
||||||
public MessageBuilder(Message msg, long recipientID, IDGenerator iDGenerator) {
|
public MessageBuilder(Message msg, long recipientID, IDGenerator iDGenerator) {
|
||||||
@ -72,79 +71,69 @@ public final class MessageBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an instance of {@link Message} with the previously supplied values.
|
* Creates an instance of {@link Message} with the previously supplied values. If a mandatory
|
||||||
* If a mandatory value is not set, a default value will be used instead:<br>
|
* value is not set, a default value will be used instead:<br>
|
||||||
* <br>
|
* <br>
|
||||||
* {@code date}
|
* {@code date} {@code Instant.now()} and {@code null} for {@code receivedDate} and
|
||||||
* {@code Instant.now()} and {@code null} for {@code receivedDate} and
|
* {@code readDate} <br>
|
||||||
* {@code readDate}
|
* {@code text} {@code ""} <br>
|
||||||
* <br>
|
* {@code status} {@code MessageStatus.WAITING}
|
||||||
* {@code text}
|
|
||||||
* {@code ""}
|
|
||||||
* <br>
|
|
||||||
* {@code status}
|
|
||||||
* {@code MessageStatus.WAITING}
|
|
||||||
*
|
*
|
||||||
* @return a new instance of {@link Message}
|
* @return a new instance of {@link Message}
|
||||||
* @since Envoy Common v0.2-alpha
|
* @since Envoy Common v0.2-alpha
|
||||||
*/
|
*/
|
||||||
public Message build() {
|
public Message build() {
|
||||||
supplyDefaults();
|
supplyDefaults();
|
||||||
return new Message(id, senderID, recipientID, creationDate, receivedDate, readDate, text, attachment, status, forwarded);
|
return new Message(id, senderID, recipientID, creationDate, receivedDate, readDate, text,
|
||||||
|
attachment, status, forwarded);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an instance of {@link GroupMessage} with the previously supplied
|
* Creates an instance of {@link GroupMessage} with the previously supplied values. <br>
|
||||||
* values. <br>
|
|
||||||
* <b> Sets all member statuses to {@link MessageStatus#WAITING}.</b><br>
|
* <b> Sets all member statuses to {@link MessageStatus#WAITING}.</b><br>
|
||||||
* If a mandatory value is not set, a default value will be used
|
* If a mandatory value is not set, a default value will be used instead:<br>
|
||||||
* instead:<br>
|
|
||||||
* <br>
|
|
||||||
* {@code time stamp}
|
|
||||||
* {@code Instant.now()}
|
|
||||||
* <br>
|
|
||||||
* {@code text}
|
|
||||||
* {@code ""}
|
|
||||||
* <br>
|
* <br>
|
||||||
|
* {@code time stamp} {@code Instant.now()} <br>
|
||||||
|
* {@code text} {@code ""} <br>
|
||||||
*
|
*
|
||||||
* @param group the {@link Group} that is used to fill the map of member
|
* @param group the {@link Group} that is used to fill the map of member statuses
|
||||||
* statuses
|
|
||||||
* @return a new instance of {@link GroupMessage}
|
* @return a new instance of {@link GroupMessage}
|
||||||
* @since Envoy Common v0.2-alpha
|
* @since Envoy Common v0.2-alpha
|
||||||
*/
|
*/
|
||||||
public GroupMessage buildGroupMessage(Group group) {
|
public GroupMessage buildGroupMessage(Group group) {
|
||||||
final var memberStatuses = new HashMap<Long, Message.MessageStatus>();
|
final var memberStatuses = new HashMap<Long, Message.MessageStatus>();
|
||||||
group.getContacts().forEach(user -> memberStatuses.put(user.getID(), MessageStatus.WAITING));
|
group.getContacts()
|
||||||
|
.forEach(user -> memberStatuses.put(user.getID(), MessageStatus.WAITING));
|
||||||
return buildGroupMessage(group, memberStatuses);
|
return buildGroupMessage(group, memberStatuses);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an instance of {@link GroupMessage} with the previously supplied
|
* Creates an instance of {@link GroupMessage} with the previously supplied values. If a
|
||||||
* values. If a mandatory value is not set, a default value will be used
|
* mandatory value is not set, a default value will be used instead:<br>
|
||||||
* instead:<br>
|
|
||||||
* <br>
|
* <br>
|
||||||
* {@code time stamp}
|
* {@code time stamp} {@code Instant.now()} <br>
|
||||||
* {@code Instant.now()}
|
* {@code text} {@code ""}
|
||||||
* <br>
|
|
||||||
* {@code text}
|
|
||||||
* {@code ""}
|
|
||||||
*
|
*
|
||||||
* @param group the {@link Group} that is used to fill the map of
|
* @param group the {@link Group} that is used to fill the map of member statuses
|
||||||
* member statuses
|
|
||||||
* @param memberStatuses the map of all current statuses
|
* @param memberStatuses the map of all current statuses
|
||||||
* @return a new instance of {@link GroupMessage}
|
* @return a new instance of {@link GroupMessage}
|
||||||
* @since Envoy Common v0.1-beta
|
* @since Envoy Common v0.1-beta
|
||||||
*/
|
*/
|
||||||
public GroupMessage buildGroupMessage(Group group, Map<Long, MessageStatus> memberStatuses) {
|
public GroupMessage buildGroupMessage(Group group, Map<Long, MessageStatus> memberStatuses) {
|
||||||
if (group == null || memberStatuses == null) throw new NullPointerException();
|
if (group == null || memberStatuses == null)
|
||||||
|
throw new NullPointerException();
|
||||||
supplyDefaults();
|
supplyDefaults();
|
||||||
return new GroupMessage(id, senderID, recipientID, creationDate, receivedDate, readDate, text, attachment, status, forwarded, memberStatuses);
|
return new GroupMessage(id, senderID, recipientID, creationDate, receivedDate, readDate,
|
||||||
|
text, attachment, status, forwarded, memberStatuses);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void supplyDefaults() {
|
private void supplyDefaults() {
|
||||||
if (creationDate == null) creationDate = Instant.now();
|
if (creationDate == null)
|
||||||
if (text == null) text = "";
|
creationDate = Instant.now();
|
||||||
if (status == null) status = MessageStatus.WAITING;
|
if (text == null)
|
||||||
|
text = "";
|
||||||
|
if (status == null)
|
||||||
|
status = MessageStatus.WAITING;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -188,8 +177,7 @@ public final class MessageBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param attachment the {@link Attachment} of the {@link Message} to
|
* @param attachment the {@link Attachment} of the {@link Message} to create
|
||||||
* create
|
|
||||||
* @return this {@link MessageBuilder}
|
* @return this {@link MessageBuilder}
|
||||||
* @since Envoy Common v0.2-alpha
|
* @since Envoy Common v0.2-alpha
|
||||||
*/
|
*/
|
||||||
|
@ -4,8 +4,7 @@ import java.io.*;
|
|||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a unique user with a unique, numeric ID, a name and a current
|
* Represents a unique user with a unique, numeric ID, a name and a current {@link UserStatus}.<br>
|
||||||
* {@link UserStatus}.<br>
|
|
||||||
*
|
*
|
||||||
* @author Kai S. K. Engelbart
|
* @author Kai S. K. Engelbart
|
||||||
* @since Envoy Common v0.2-alpha
|
* @since Envoy Common v0.2-alpha
|
||||||
@ -34,8 +33,7 @@ public final class User extends Contact {
|
|||||||
ONLINE,
|
ONLINE,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* select this, if a user is online but unavailable at the moment (sudden
|
* select this, if a user is online but unavailable at the moment (sudden interruption)
|
||||||
* interruption)
|
|
||||||
*/
|
*/
|
||||||
AWAY,
|
AWAY,
|
||||||
|
|
||||||
@ -52,8 +50,7 @@ public final class User extends Contact {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes a {@link User}. <br>
|
* Initializes a {@link User}. <br>
|
||||||
* The {@link UserStatus} is set to {@link UserStatus#ONLINE}.
|
* The {@link UserStatus} is set to {@link UserStatus#ONLINE}. No contacts are initialized.
|
||||||
* No contacts are initialized.
|
|
||||||
*
|
*
|
||||||
* @param id unique ID
|
* @param id unique ID
|
||||||
* @param name user name
|
* @param name user name
|
||||||
@ -89,12 +86,13 @@ public final class User extends Contact {
|
|||||||
*/
|
*/
|
||||||
public User(long id, String name, UserStatus status, Set<Contact> contacts) {
|
public User(long id, String name, UserStatus status, Set<Contact> contacts) {
|
||||||
super(id, name, contacts);
|
super(id, name, contacts);
|
||||||
this.status = status;
|
this.status = Objects.requireNonNull(status);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return String.format("User[id=%d,name=%s,status=%s", id, name, status) + (contacts.isEmpty() ? "]" : "," + contacts.size() + " contact(s)]");
|
return String.format("User[id=%d,name=%s,status=%s", id, name, status)
|
||||||
|
+ (contacts.isEmpty() ? "]" : "," + contacts.size() + " contact(s)]");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -119,15 +117,18 @@ public final class User extends Contact {
|
|||||||
private void writeObject(ObjectOutputStream outputStream) throws Exception {
|
private void writeObject(ObjectOutputStream outputStream) throws Exception {
|
||||||
outputStream.defaultWriteObject();
|
outputStream.defaultWriteObject();
|
||||||
if (serializeContacts) {
|
if (serializeContacts) {
|
||||||
getContacts().stream().filter(User.class::isInstance).map(User.class::cast).forEach(user -> user.serializeContacts = false);
|
getContacts().stream().filter(User.class::isInstance).map(User.class::cast)
|
||||||
|
.forEach(user -> user.serializeContacts = false);
|
||||||
outputStream.writeObject(getContacts());
|
outputStream.writeObject(getContacts());
|
||||||
} else outputStream.writeObject(new HashSet<>());
|
} else
|
||||||
|
outputStream.writeObject(new HashSet<>());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param serializeContacts whether the contacts of this {@link User} should be
|
* @param serializeContacts whether the contacts of this {@link User} should be serialized
|
||||||
* serialized
|
|
||||||
* @since Envoy Common v0.1-beta
|
* @since Envoy Common v0.1-beta
|
||||||
*/
|
*/
|
||||||
public void serializeContacts(boolean serializeContacts) { this.serializeContacts = serializeContacts; }
|
public void serializeContacts(boolean serializeContacts) {
|
||||||
|
this.serializeContacts = serializeContacts;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* This package contains all data objects that are used both by Envoy Client and
|
* This package contains all data objects that are used both by Envoy Client and by Envoy Server.
|
||||||
* by Envoy Server.
|
|
||||||
*
|
*
|
||||||
* @author Leon Hofmeister
|
* @author Leon Hofmeister
|
||||||
* @author Maximilian Käfer
|
* @author Maximilian Käfer
|
||||||
|
@ -3,8 +3,7 @@ package envoy.event;
|
|||||||
/**
|
/**
|
||||||
* This enum declares all modification possibilities for a given container.
|
* This enum declares all modification possibilities for a given container.
|
||||||
* <p>
|
* <p>
|
||||||
* These can be: {@link ElementOperation#ADD} or
|
* These can be: {@link ElementOperation#ADD} or {@link ElementOperation#REMOVE}.
|
||||||
* {@link ElementOperation#REMOVE}.
|
|
||||||
*
|
*
|
||||||
* @author Leon Hofmeister
|
* @author Leon Hofmeister
|
||||||
* @since Envoy Common v0.1-beta
|
* @since Envoy Common v0.1-beta
|
||||||
@ -12,14 +11,12 @@ package envoy.event;
|
|||||||
public enum ElementOperation {
|
public enum ElementOperation {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Select this element, if the given element should be added to the given
|
* Select this element, if the given element should be added to the given container.
|
||||||
* container.
|
|
||||||
*/
|
*/
|
||||||
ADD,
|
ADD,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Select this element, if the given element should be removed from the given
|
* Select this element, if the given element should be removed from the given container.
|
||||||
* container.
|
|
||||||
*/
|
*/
|
||||||
REMOVE
|
REMOVE
|
||||||
}
|
}
|
||||||
|
@ -1,33 +1,46 @@
|
|||||||
package envoy.event;
|
package envoy.event;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.util.Objects;
|
||||||
import dev.kske.eventbus.IEvent;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class serves as a convenience base class for all events. It implements
|
* This class serves as a convenience base class for all events. It provides a generic value. For
|
||||||
* the {@link IEvent} interface and provides a generic value. For events without
|
* events without a value there also is {@link envoy.event.Event.Valueless}.
|
||||||
* a value there also is {@link envoy.event.Event.Valueless}.
|
|
||||||
*
|
*
|
||||||
* @author Kai S. K. Engelbart
|
* @author Kai S. K. Engelbart
|
||||||
* @param <T> the type of the Event
|
* @param <T> the type of the Event
|
||||||
* @since Envoy v0.2-alpha
|
* @since Envoy v0.2-alpha
|
||||||
*/
|
*/
|
||||||
public abstract class Event<T> implements IEvent, Serializable {
|
public abstract class Event<T> implements Serializable {
|
||||||
|
|
||||||
protected final T value;
|
protected final T value;
|
||||||
|
|
||||||
private static final long serialVersionUID = 0L;
|
private static final long serialVersionUID = 0L;
|
||||||
|
|
||||||
protected Event(T value) { this.value = value; }
|
protected Event(T value) {
|
||||||
|
this(value, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This constructor is reserved for {@link Valueless} events. No other event should contain null
|
||||||
|
* values. Only use if really necessary. Using this constructor with {@code true} implies that
|
||||||
|
* the user has to manually check if the value of the event is null.
|
||||||
|
*/
|
||||||
|
protected Event(T value, boolean canBeNull) {
|
||||||
|
this.value = canBeNull ? value : Objects.requireNonNull(value);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the data associated with this event
|
* @return the data associated with this event
|
||||||
*/
|
*/
|
||||||
public T get() { return value; }
|
public T get() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() { return String.format("%s[value=%s]", this.getClass().getSimpleName(), value); }
|
public String toString() {
|
||||||
|
return String.format("%s[value=%s]", this.getClass().getSimpleName(), value);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Serves as a super class for events that do not carry a value.
|
* Serves as a super class for events that do not carry a value.
|
||||||
@ -39,9 +52,13 @@ public abstract class Event<T> implements IEvent, Serializable {
|
|||||||
|
|
||||||
private static final long serialVersionUID = 0L;
|
private static final long serialVersionUID = 0L;
|
||||||
|
|
||||||
protected Valueless() { super(null); }
|
protected Valueless() {
|
||||||
|
super(null, true);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() { return this.getClass().getSimpleName(); }
|
public String toString() {
|
||||||
|
return this.getClass().getSimpleName();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,20 +17,19 @@ public final class GroupCreation extends Event<String> {
|
|||||||
private static final long serialVersionUID = 0L;
|
private static final long serialVersionUID = 0L;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param value the name of this group at creation time
|
* @param name the name of this group at creation time
|
||||||
* @param initialMemberIDs the IDs of all {@link User}s that should be group
|
* @param initialMemberIDs the IDs of all {@link User}s that should be group members from the
|
||||||
* members from the beginning on (excluding the creator
|
* beginning on (excluding the creator of this group)
|
||||||
* of this group)
|
|
||||||
* @since Envoy Common v0.1-beta
|
* @since Envoy Common v0.1-beta
|
||||||
*/
|
*/
|
||||||
public GroupCreation(String value, Set<Long> initialMemberIDs) {
|
public GroupCreation(String name, Set<Long> initialMemberIDs) {
|
||||||
super(value);
|
super(name);
|
||||||
this.initialMemberIDs = initialMemberIDs != null ? initialMemberIDs : new HashSet<>();
|
this.initialMemberIDs = initialMemberIDs != null ? initialMemberIDs : new HashSet<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the IDs of all {@link User}s that are members from the beginning
|
* @return the IDs of all {@link User}s that are members from the beginning (excluding the
|
||||||
* (excluding the creator of this group)
|
* creator of this group)
|
||||||
* @since Envoy Common v0.1-beta
|
* @since Envoy Common v0.1-beta
|
||||||
*/
|
*/
|
||||||
public Set<Long> getInitialMemberIDs() { return initialMemberIDs; }
|
public Set<Long> getInitialMemberIDs() { return initialMemberIDs; }
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user