Merge remote-tracking branch 'client/develop' into develop
32
.classpath
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<classpath>
|
||||||
|
<classpathentry kind="src" output="target/classes" path="src/main/java">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="optional" value="true"/>
|
||||||
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="optional" value="true"/>
|
||||||
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
|
<attribute name="test" value="true"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry kind="output" path="target/classes"/>
|
||||||
|
</classpath>
|
1
.github/CODEOWNERS
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
* @CyB3RC0nN0R
|
38
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Create a report to help us improve
|
||||||
|
title: ''
|
||||||
|
labels: bug
|
||||||
|
assignees: CyB3RC0nN0R, delvh, DieGurke, derharry333
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Describe the bug**
|
||||||
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
|
**To Reproduce**
|
||||||
|
Steps to reproduce the behavior:
|
||||||
|
1. Go to '...'
|
||||||
|
2. Click on '....'
|
||||||
|
3. Scroll down to '....'
|
||||||
|
4. See error
|
||||||
|
|
||||||
|
**Expected behavior**
|
||||||
|
A clear and concise description of what you expected to happen.
|
||||||
|
|
||||||
|
**Screenshots**
|
||||||
|
If applicable, add screenshots to help explain your problem.
|
||||||
|
|
||||||
|
**Desktop (please complete the following information):**
|
||||||
|
- OS: [e.g. iOS]
|
||||||
|
- Browser [e.g. chrome, safari]
|
||||||
|
- Version [e.g. 22]
|
||||||
|
|
||||||
|
**Smartphone (please complete the following information):**
|
||||||
|
- Device: [e.g. iPhone6]
|
||||||
|
- OS: [e.g. iOS8.1]
|
||||||
|
- Browser [e.g. stock browser, safari]
|
||||||
|
- Version [e.g. 22]
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context about the problem here.
|
22
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an idea for this project
|
||||||
|
title: ''
|
||||||
|
labels: enhancement, feature
|
||||||
|
assignees: CyB3RC0nN0R, delvh, DieGurke
|
||||||
|
project: Envoy
|
||||||
|
milestones: Envoy v0.3-alpha
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Is your feature request related to a problem? Please describe.**
|
||||||
|
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||||
|
|
||||||
|
**Describe the solution you'd like**
|
||||||
|
A clear and concise description of what you want to happen.
|
||||||
|
|
||||||
|
**Describe alternatives you've considered**
|
||||||
|
A clear and concise description of any alternative solutions or features you've considered.
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context or screenshots about the feature request here.
|
11
.github/PULL_REQUEST_TEMPLATE/bugfix.md
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
---
|
||||||
|
name: Bug fix
|
||||||
|
title: Fixed Bug
|
||||||
|
labels: bug
|
||||||
|
assignees: CyB3RC0nN0R, delvh, DieGurke
|
||||||
|
reviewers: CyB3RC0nN0R, delvh
|
||||||
|
projects: Envoy
|
||||||
|
milestone: Envoy v0.1-beta
|
||||||
|
|
||||||
|
---
|
||||||
|
Fixes #{issue}
|
10
.github/PULL_REQUEST_TEMPLATE/feature_integration.md
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
name: Feature integration
|
||||||
|
title: Added feature
|
||||||
|
labels: feature
|
||||||
|
assignees: CyB3RC0nN0R, delvh, DieGurke
|
||||||
|
reviewers: CyB3RC0nN0R, delvh
|
||||||
|
projects: Envoy
|
||||||
|
milestone: Envoy v0.1-beta
|
||||||
|
|
||||||
|
---
|
10
.github/PULL_REQUEST_TEMPLATE/javadoc_update.md
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
name: Updated Javadoc
|
||||||
|
title: Updated Javadoc
|
||||||
|
labels: documentation
|
||||||
|
assignees: CyB3RC0nN0R, delvh
|
||||||
|
reviewers: CyB3RC0nN0R, delvh
|
||||||
|
projects: Envoy
|
||||||
|
milestone: Envoy v0.1-beta
|
||||||
|
|
||||||
|
---
|
28
.github/workflows/maven.yml
vendored
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
name: Java CI
|
||||||
|
|
||||||
|
on: [push]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Set up JDK 11
|
||||||
|
uses: actions/setup-java@v1
|
||||||
|
with:
|
||||||
|
java-version: 11
|
||||||
|
- name: Cache Maven packages
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: ~/.m2
|
||||||
|
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
|
||||||
|
restore-keys: ${{ runner.os }}-m2
|
||||||
|
- name: Build with Maven
|
||||||
|
run: mvn -B package --file pom.xml
|
||||||
|
- name: Stage build artifacts
|
||||||
|
run: mkdir staging && cp target/*.jar staging
|
||||||
|
- uses: actions/upload-artifact@v1
|
||||||
|
with:
|
||||||
|
name: envoy-client-artifacts
|
||||||
|
path: staging
|
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
/target/
|
||||||
|
|
||||||
|
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
||||||
|
hs_err_pid*
|
38
.project
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<projectDescription>
|
||||||
|
<name>envoy-client</name>
|
||||||
|
<comment></comment>
|
||||||
|
<projects>
|
||||||
|
</projects>
|
||||||
|
<buildSpec>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.jboss.tools.jst.web.kb.kbbuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.jboss.tools.cdi.core.cdibuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.wst.validation.validationbuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
</buildSpec>
|
||||||
|
<natures>
|
||||||
|
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||||
|
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||||
|
</natures>
|
||||||
|
</projectDescription>
|
6
.settings/org.eclipse.core.resources.prefs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
eclipse.preferences.version=1
|
||||||
|
encoding//src/main/java=UTF-8
|
||||||
|
encoding//src/main/resources=UTF-8
|
||||||
|
encoding//src/test/java=UTF-8
|
||||||
|
encoding//src/test/resources=UTF-8
|
||||||
|
encoding/<project>=UTF-8
|
491
.settings/org.eclipse.jdt.core.prefs
Normal file
@ -0,0 +1,491 @@
|
|||||||
|
eclipse.preferences.version=1
|
||||||
|
org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=disabled
|
||||||
|
org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore
|
||||||
|
org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull
|
||||||
|
org.eclipse.jdt.core.compiler.annotation.nonnull.secondary=
|
||||||
|
org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault
|
||||||
|
org.eclipse.jdt.core.compiler.annotation.nonnullbydefault.secondary=
|
||||||
|
org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable
|
||||||
|
org.eclipse.jdt.core.compiler.annotation.nullable.secondary=
|
||||||
|
org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled
|
||||||
|
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
|
||||||
|
org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
|
||||||
|
org.eclipse.jdt.core.compiler.codegen.targetPlatform=11
|
||||||
|
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
|
||||||
|
org.eclipse.jdt.core.compiler.compliance=11
|
||||||
|
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
|
||||||
|
org.eclipse.jdt.core.compiler.debug.localVariable=generate
|
||||||
|
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
|
||||||
|
org.eclipse.jdt.core.compiler.doc.comment.support=enabled
|
||||||
|
org.eclipse.jdt.core.compiler.problem.APILeak=warning
|
||||||
|
org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning
|
||||||
|
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
|
||||||
|
org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
|
||||||
|
org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning
|
||||||
|
org.eclipse.jdt.core.compiler.problem.deadCode=warning
|
||||||
|
org.eclipse.jdt.core.compiler.problem.deprecation=warning
|
||||||
|
org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
|
||||||
|
org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled
|
||||||
|
org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
|
||||||
|
org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore
|
||||||
|
org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
|
||||||
|
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
|
||||||
|
org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore
|
||||||
|
org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore
|
||||||
|
org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled
|
||||||
|
org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore
|
||||||
|
org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning
|
||||||
|
org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning
|
||||||
|
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
|
||||||
|
org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning
|
||||||
|
org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=disabled
|
||||||
|
org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning
|
||||||
|
org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore
|
||||||
|
org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore
|
||||||
|
org.eclipse.jdt.core.compiler.problem.invalidJavadoc=info
|
||||||
|
org.eclipse.jdt.core.compiler.problem.invalidJavadocTags=enabled
|
||||||
|
org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsDeprecatedRef=enabled
|
||||||
|
org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsNotVisibleRef=enabled
|
||||||
|
org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsVisibility=public
|
||||||
|
org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore
|
||||||
|
org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning
|
||||||
|
org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore
|
||||||
|
org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore
|
||||||
|
org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled
|
||||||
|
org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=ignore
|
||||||
|
org.eclipse.jdt.core.compiler.problem.missingJavadocComments=info
|
||||||
|
org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsOverriding=disabled
|
||||||
|
org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsVisibility=public
|
||||||
|
org.eclipse.jdt.core.compiler.problem.missingJavadocTagDescription=all_standard_tags
|
||||||
|
org.eclipse.jdt.core.compiler.problem.missingJavadocTags=info
|
||||||
|
org.eclipse.jdt.core.compiler.problem.missingJavadocTagsMethodTypeParameters=disabled
|
||||||
|
org.eclipse.jdt.core.compiler.problem.missingJavadocTagsOverriding=disabled
|
||||||
|
org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=public
|
||||||
|
org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore
|
||||||
|
org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled
|
||||||
|
org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning
|
||||||
|
org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore
|
||||||
|
org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning
|
||||||
|
org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning
|
||||||
|
org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore
|
||||||
|
org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning
|
||||||
|
org.eclipse.jdt.core.compiler.problem.nonnullTypeVariableFromLegacyInvocation=warning
|
||||||
|
org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error
|
||||||
|
org.eclipse.jdt.core.compiler.problem.nullReference=warning
|
||||||
|
org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error
|
||||||
|
org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning
|
||||||
|
org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning
|
||||||
|
org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore
|
||||||
|
org.eclipse.jdt.core.compiler.problem.pessimisticNullAnalysisForFreeTypeVariables=warning
|
||||||
|
org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=ignore
|
||||||
|
org.eclipse.jdt.core.compiler.problem.potentialNullReference=ignore
|
||||||
|
org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore
|
||||||
|
org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning
|
||||||
|
org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning
|
||||||
|
org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore
|
||||||
|
org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=warning
|
||||||
|
org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=warning
|
||||||
|
org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore
|
||||||
|
org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore
|
||||||
|
org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning
|
||||||
|
org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled
|
||||||
|
org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning
|
||||||
|
org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled
|
||||||
|
org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
|
||||||
|
org.eclipse.jdt.core.compiler.problem.suppressWarningsNotFullyAnalysed=info
|
||||||
|
org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled
|
||||||
|
org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
|
||||||
|
org.eclipse.jdt.core.compiler.problem.terminalDeprecation=warning
|
||||||
|
org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
|
||||||
|
org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=disabled
|
||||||
|
org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=ignore
|
||||||
|
org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning
|
||||||
|
org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore
|
||||||
|
org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
|
||||||
|
org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentType=warning
|
||||||
|
org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentTypeStrict=disabled
|
||||||
|
org.eclipse.jdt.core.compiler.problem.unlikelyEqualsArgumentType=info
|
||||||
|
org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
|
||||||
|
org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=ignore
|
||||||
|
org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
|
||||||
|
org.eclipse.jdt.core.compiler.problem.unstableAutoModuleName=warning
|
||||||
|
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore
|
||||||
|
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
|
||||||
|
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
|
||||||
|
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
|
||||||
|
org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore
|
||||||
|
org.eclipse.jdt.core.compiler.problem.unusedImport=warning
|
||||||
|
org.eclipse.jdt.core.compiler.problem.unusedLabel=warning
|
||||||
|
org.eclipse.jdt.core.compiler.problem.unusedLocal=warning
|
||||||
|
org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=ignore
|
||||||
|
org.eclipse.jdt.core.compiler.problem.unusedParameter=warning
|
||||||
|
org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled
|
||||||
|
org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled
|
||||||
|
org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled
|
||||||
|
org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning
|
||||||
|
org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore
|
||||||
|
org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
|
||||||
|
org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
|
||||||
|
org.eclipse.jdt.core.compiler.release=disabled
|
||||||
|
org.eclipse.jdt.core.compiler.source=11
|
||||||
|
org.eclipse.jdt.core.formatter.align_assignment_statements_on_columns=true
|
||||||
|
org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines=1
|
||||||
|
org.eclipse.jdt.core.formatter.align_type_members_on_columns=true
|
||||||
|
org.eclipse.jdt.core.formatter.align_variable_declarations_on_columns=true
|
||||||
|
org.eclipse.jdt.core.formatter.align_with_spaces=false
|
||||||
|
org.eclipse.jdt.core.formatter.alignment_for_additive_operator=16
|
||||||
|
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
|
||||||
|
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=84
|
||||||
|
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
|
||||||
|
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
|
||||||
|
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=80
|
||||||
|
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=20
|
||||||
|
org.eclipse.jdt.core.formatter.alignment_for_assignment=0
|
||||||
|
org.eclipse.jdt.core.formatter.alignment_for_bitwise_operator=16
|
||||||
|
org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
|
||||||
|
org.eclipse.jdt.core.formatter.alignment_for_compact_loops=16
|
||||||
|
org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80
|
||||||
|
org.eclipse.jdt.core.formatter.alignment_for_conditional_expression_chain=0
|
||||||
|
org.eclipse.jdt.core.formatter.alignment_for_enum_constants=16
|
||||||
|
org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
|
||||||
|
org.eclipse.jdt.core.formatter.alignment_for_expressions_in_for_loop_header=0
|
||||||
|
org.eclipse.jdt.core.formatter.alignment_for_logical_operator=16
|
||||||
|
org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0
|
||||||
|
org.eclipse.jdt.core.formatter.alignment_for_module_statements=16
|
||||||
|
org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
|
||||||
|
org.eclipse.jdt.core.formatter.alignment_for_multiplicative_operator=16
|
||||||
|
org.eclipse.jdt.core.formatter.alignment_for_parameterized_type_references=0
|
||||||
|
org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
|
||||||
|
org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
|
||||||
|
org.eclipse.jdt.core.formatter.alignment_for_relational_operator=0
|
||||||
|
org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=80
|
||||||
|
org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=84
|
||||||
|
org.eclipse.jdt.core.formatter.alignment_for_shift_operator=0
|
||||||
|
org.eclipse.jdt.core.formatter.alignment_for_string_concatenation=16
|
||||||
|
org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
|
||||||
|
org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
|
||||||
|
org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
|
||||||
|
org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
|
||||||
|
org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
|
||||||
|
org.eclipse.jdt.core.formatter.alignment_for_type_arguments=0
|
||||||
|
org.eclipse.jdt.core.formatter.alignment_for_type_parameters=0
|
||||||
|
org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16
|
||||||
|
org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
|
||||||
|
org.eclipse.jdt.core.formatter.blank_lines_after_last_class_body_declaration=0
|
||||||
|
org.eclipse.jdt.core.formatter.blank_lines_after_package=1
|
||||||
|
org.eclipse.jdt.core.formatter.blank_lines_before_abstract_method=1
|
||||||
|
org.eclipse.jdt.core.formatter.blank_lines_before_field=0
|
||||||
|
org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=1
|
||||||
|
org.eclipse.jdt.core.formatter.blank_lines_before_imports=1
|
||||||
|
org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1
|
||||||
|
org.eclipse.jdt.core.formatter.blank_lines_before_method=1
|
||||||
|
org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
|
||||||
|
org.eclipse.jdt.core.formatter.blank_lines_before_package=0
|
||||||
|
org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
|
||||||
|
org.eclipse.jdt.core.formatter.blank_lines_between_statement_group_in_switch=0
|
||||||
|
org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1
|
||||||
|
org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
|
||||||
|
org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
|
||||||
|
org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
|
||||||
|
org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
|
||||||
|
org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
|
||||||
|
org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
|
||||||
|
org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
|
||||||
|
org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
|
||||||
|
org.eclipse.jdt.core.formatter.brace_position_for_lambda_body=end_of_line
|
||||||
|
org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
|
||||||
|
org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
|
||||||
|
org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
|
||||||
|
org.eclipse.jdt.core.formatter.comment.align_tags_descriptions_grouped=true
|
||||||
|
org.eclipse.jdt.core.formatter.comment.align_tags_names_descriptions=false
|
||||||
|
org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=true
|
||||||
|
org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
|
||||||
|
org.eclipse.jdt.core.formatter.comment.count_line_length_from_starting_position=true
|
||||||
|
org.eclipse.jdt.core.formatter.comment.format_block_comments=true
|
||||||
|
org.eclipse.jdt.core.formatter.comment.format_header=true
|
||||||
|
org.eclipse.jdt.core.formatter.comment.format_html=true
|
||||||
|
org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
|
||||||
|
org.eclipse.jdt.core.formatter.comment.format_line_comments=true
|
||||||
|
org.eclipse.jdt.core.formatter.comment.format_source_code=true
|
||||||
|
org.eclipse.jdt.core.formatter.comment.indent_parameter_description=false
|
||||||
|
org.eclipse.jdt.core.formatter.comment.indent_root_tags=false
|
||||||
|
org.eclipse.jdt.core.formatter.comment.indent_tag_description=false
|
||||||
|
org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
|
||||||
|
org.eclipse.jdt.core.formatter.comment.insert_new_line_between_different_tags=do not insert
|
||||||
|
org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert
|
||||||
|
org.eclipse.jdt.core.formatter.comment.line_length=80
|
||||||
|
org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true
|
||||||
|
org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true
|
||||||
|
org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false
|
||||||
|
org.eclipse.jdt.core.formatter.compact_else_if=true
|
||||||
|
org.eclipse.jdt.core.formatter.continuation_indentation=2
|
||||||
|
org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
|
||||||
|
org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off
|
||||||
|
org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on
|
||||||
|
org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=true
|
||||||
|
org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true
|
||||||
|
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
|
||||||
|
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
|
||||||
|
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
|
||||||
|
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
|
||||||
|
org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
|
||||||
|
org.eclipse.jdt.core.formatter.indent_empty_lines=false
|
||||||
|
org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
|
||||||
|
org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
|
||||||
|
org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
|
||||||
|
org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=true
|
||||||
|
org.eclipse.jdt.core.formatter.indentation.size=4
|
||||||
|
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_enum_constant=insert
|
||||||
|
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert
|
||||||
|
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
|
||||||
|
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert
|
||||||
|
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert
|
||||||
|
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
|
||||||
|
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert
|
||||||
|
org.eclipse.jdt.core.formatter.insert_new_line_after_label=insert
|
||||||
|
org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
|
||||||
|
org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation=do not insert
|
||||||
|
org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=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
|
9
.settings/org.eclipse.jdt.ui.prefs
Normal file
4
.settings/org.eclipse.m2e.core.prefs
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
activeProfiles=
|
||||||
|
eclipse.preferences.version=1
|
||||||
|
resolveWorkspaceProjects=true
|
||||||
|
version=1
|
3
.settings/org.hibernate.eclipse.console.prefs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
default.configuration=
|
||||||
|
eclipse.preferences.version=1
|
||||||
|
hibernate3.enabled=false
|
76
CODE_OF_CONDUCT.md
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
# Contributor Covenant Code of Conduct
|
||||||
|
|
||||||
|
## Our Pledge
|
||||||
|
|
||||||
|
In the interest of fostering an open and welcoming environment, we as
|
||||||
|
contributors and maintainers pledge to making participation in our project and
|
||||||
|
our community a harassment-free experience for everyone, regardless of age, body
|
||||||
|
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||||
|
level of experience, education, socio-economic status, nationality, personal
|
||||||
|
appearance, race, religion, or sexual identity and orientation.
|
||||||
|
|
||||||
|
## Our Standards
|
||||||
|
|
||||||
|
Examples of behavior that contributes to creating a positive environment
|
||||||
|
include:
|
||||||
|
|
||||||
|
* Using welcoming and inclusive language
|
||||||
|
* Being respectful of differing viewpoints and experiences
|
||||||
|
* Gracefully accepting constructive criticism
|
||||||
|
* Focusing on what is best for the community
|
||||||
|
* Showing empathy towards other community members
|
||||||
|
|
||||||
|
Examples of unacceptable behavior by participants include:
|
||||||
|
|
||||||
|
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||||
|
advances
|
||||||
|
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||||
|
* Public or private harassment
|
||||||
|
* Publishing others' private information, such as a physical or electronic
|
||||||
|
address, without explicit permission
|
||||||
|
* Other conduct which could reasonably be considered inappropriate in a
|
||||||
|
professional setting
|
||||||
|
|
||||||
|
## Our Responsibilities
|
||||||
|
|
||||||
|
Project maintainers are responsible for clarifying the standards of acceptable
|
||||||
|
behavior and are expected to take appropriate and fair corrective action in
|
||||||
|
response to any instances of unacceptable behavior.
|
||||||
|
|
||||||
|
Project maintainers have the right and responsibility to remove, edit, or
|
||||||
|
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||||
|
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||||
|
permanently any contributor for other behaviors that they deem inappropriate,
|
||||||
|
threatening, offensive, or harmful.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This Code of Conduct applies both within project spaces and in public spaces
|
||||||
|
when an individual is representing the project or its community. Examples of
|
||||||
|
representing a project or community include using an official project e-mail
|
||||||
|
address, posting via an official social media account, or acting as an appointed
|
||||||
|
representative at an online or offline event. Representation of a project may be
|
||||||
|
further defined and clarified by project maintainers.
|
||||||
|
|
||||||
|
## Enforcement
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
|
reported by contacting the project team at kske@outlook.de. All
|
||||||
|
complaints will be reviewed and investigated and will result in a response that
|
||||||
|
is deemed necessary and appropriate to the circumstances. The project team is
|
||||||
|
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||||
|
Further details of specific enforcement policies may be posted separately.
|
||||||
|
|
||||||
|
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||||
|
faith may face temporary or permanent repercussions as determined by other
|
||||||
|
members of the project's leadership.
|
||||||
|
|
||||||
|
## Attribution
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||||
|
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||||
|
|
||||||
|
[homepage]: https://www.contributor-covenant.org
|
||||||
|
|
||||||
|
For answers to common questions about this code of conduct, see
|
||||||
|
https://www.contributor-covenant.org/faq
|
161
CONTRIBUTING.md
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
# Contributing to Envoy
|
||||||
|
|
||||||
|
Looking to contribute something to Envoy? **Here's how you can help.**
|
||||||
|
|
||||||
|
Please take a moment to review this document in order to make the contribution
|
||||||
|
process easy and effective for everyone involved.
|
||||||
|
|
||||||
|
Following these guidelines helps to communicate that you respect the time of
|
||||||
|
the developers managing and developing this open source project. In return,
|
||||||
|
they should reciprocate that respect in addressing your issue or assessing
|
||||||
|
patches and features.
|
||||||
|
|
||||||
|
|
||||||
|
## Using the issue tracker
|
||||||
|
|
||||||
|
The [issue tracker](https://github.com/informatik-ag-ngl/envoy-client/issues) is
|
||||||
|
the preferred channel for [bug reports](#bug-reports), [features requests](#feature-requests)
|
||||||
|
and [submitting pull requests](#pull-requests), but please respect the following
|
||||||
|
restrictions:
|
||||||
|
|
||||||
|
* Please **do not** derail or troll issues. Keep the discussion on topic and
|
||||||
|
respect the opinions of others.
|
||||||
|
|
||||||
|
* Please **do not** post comments consisting solely of "+1" or ":thumbsup:".
|
||||||
|
Use [GitHub's "reactions" feature](https://blog.github.com/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/)
|
||||||
|
instead. We reserve the right to delete comments which violate this rule.
|
||||||
|
|
||||||
|
However, as we know, we are all software engineers that like being funny hence doing it on purpose. Please also refrain from that kind of behaviour.
|
||||||
|
|
||||||
|
## Issues and labels
|
||||||
|
|
||||||
|
Our bug tracker utilizes several labels to help organize and identify issues. Here's what they represent and how we use them:
|
||||||
|
|
||||||
|
- `Documentation` & `Javadoc`- Issues regarding the documentation of Envoy
|
||||||
|
- `Enhancement` & `Feature` - Issues suggesting a new feature
|
||||||
|
- `Maven` - Issues concerned with Maven problems
|
||||||
|
- `Bug` - Issues concerned with a general bug
|
||||||
|
|
||||||
|
For a complete look at our labels, see the [project labels page](https://github.com/informatik-ag-ngl/envoy-client/labels).
|
||||||
|
|
||||||
|
## Bug reports
|
||||||
|
|
||||||
|
A bug is a _demonstrable problem_ that is caused by the code in the repository.
|
||||||
|
Good bug reports are extremely helpful, so thanks!
|
||||||
|
|
||||||
|
Guidelines for bug reports:
|
||||||
|
|
||||||
|
0. **ensure your problem isn't caused by a simple error in your own code**.
|
||||||
|
|
||||||
|
1. **Use the GitHub issue search** — check if the issue has already been
|
||||||
|
reported.
|
||||||
|
|
||||||
|
2. **Check if the issue has been fixed** — try to reproduce it using the
|
||||||
|
latest `master` or development branch in the repository.
|
||||||
|
|
||||||
|
3. **Isolate the problem** — ideally create a reduced test
|
||||||
|
case and a live example.
|
||||||
|
|
||||||
|
|
||||||
|
A good bug report shouldn't leave others needing to chase you up for more
|
||||||
|
information. Please try to be as detailed as possible in your report. What is
|
||||||
|
your environment? What steps will reproduce the issue? These details will help people to fix
|
||||||
|
any potential bugs.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
> Short and descriptive example bug report title
|
||||||
|
>
|
||||||
|
> 1. This is the first step
|
||||||
|
> 2. This is the second step
|
||||||
|
> 3. Further steps, etc.
|
||||||
|
>
|
||||||
|
> Any other information you want to share that is relevant to the issue being
|
||||||
|
> reported. This might include the lines of code that you have identified as
|
||||||
|
> causing the bug, and potential solutions (and your opinions on their
|
||||||
|
> merits).
|
||||||
|
|
||||||
|
## Feature requests
|
||||||
|
|
||||||
|
Feature requests are welcome. But take a moment to find out whether your idea
|
||||||
|
fits with the scope and aims of the project. It's up to *you* to make a strong
|
||||||
|
case to convince the project's developers of the merits of this feature. Please
|
||||||
|
provide as much detail and context as possible.
|
||||||
|
|
||||||
|
|
||||||
|
## Pull requests
|
||||||
|
|
||||||
|
Good pull requests—patches, improvements, new features—are a fantastic
|
||||||
|
help. They should remain focused in scope and avoid containing unrelated
|
||||||
|
commits.
|
||||||
|
|
||||||
|
**Please ask first** before embarking on any significant pull request (e.g.
|
||||||
|
implementing features, refactoring code, porting to a different language),
|
||||||
|
otherwise you risk spending a lot of time working on something that the
|
||||||
|
project's developers might not want to merge into the project.
|
||||||
|
|
||||||
|
Please adhere to the [coding guidelines](#code-guidelines) used throughout the
|
||||||
|
project (indentation, accurate comments, etc.) and any other requirements
|
||||||
|
(such as test coverage).
|
||||||
|
|
||||||
|
Adhering to the following process is the best way to get your work
|
||||||
|
included in the project:
|
||||||
|
|
||||||
|
1. Download, clone or [Fork](https://help.github.com/articles/fork-a-repo/) the project, using [https://github.com/informatik-ag-ngl/envoy-client/](https://github.com/informatik-ag-ngl/envoy-client/)as Remote.
|
||||||
|
|
||||||
|
2. If you cloned a while ago, get the latest changes from upstream:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git checkout master
|
||||||
|
git pull upstream master
|
||||||
|
```
|
||||||
|
Or, if your IDE of choice supports this, simply use `pull`
|
||||||
|
|
||||||
|
3. Create a new topic branch (off the main project development branch) to
|
||||||
|
contain your feature, change, or fix:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git checkout -b <topic-branch-name>
|
||||||
|
```
|
||||||
|
Or, simply use "New branch" if your IDE supports this
|
||||||
|
|
||||||
|
4. Commit your changes in logical chunks. Please adhere to these [git commit
|
||||||
|
message guidelines](https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)
|
||||||
|
or your code is unlikely be merged into the main project. Use Git's
|
||||||
|
[interactive rebase](https://help.github.com/articles/about-git-rebase/)
|
||||||
|
feature to tidy up your commits before making them public.
|
||||||
|
|
||||||
|
5. Locally merge (or rebase) the upstream development branch into your topic branch:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git pull [--rebase] upstream master
|
||||||
|
```
|
||||||
|
|
||||||
|
6. Push your topic branch up to your fork:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git push origin <topic-branch-name>
|
||||||
|
```
|
||||||
|
|
||||||
|
7. [Open a Pull Request](https://help.github.com/articles/about-pull-requests/)
|
||||||
|
with a clear title and description against the `master` branch.
|
||||||
|
|
||||||
|
**IMPORTANT**: By submitting a patch, you agree to allow the project owners to
|
||||||
|
license your work under the terms of the [MIT License](../LICENSE) (if it
|
||||||
|
includes code changes) and under the terms of the
|
||||||
|
[Creative Commons Attribution 3.0 Unported License](https://creativecommons.org/licenses/by/3.0/)
|
||||||
|
(if it includes documentation changes).
|
||||||
|
|
||||||
|
|
||||||
|
## Code guidelines
|
||||||
|
|
||||||
|
### Java
|
||||||
|
|
||||||
|
Please use the formatter provided with this project. Especially before saving. For best results, select the option "format code" in the "Save Actions" tab in Preferences in Eclipse, so that you never accidentally forget it.
|
||||||
|
Every public function (not annotated with `@Override`) must be delivered with Javadoc. For best project-appropriate Javadoc please take a look at the other functions which are all already equipped with Javadoc.
|
||||||
|
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
By contributing your code, you agree to license your contribution under the [MIT License](../LICENSE).
|
||||||
|
By contributing to the documentation, you agree to license your contribution under the [Creative Commons Attribution 3.0 Unported License](https://creativecommons.org/licenses/by/3.0/).
|
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2019 Informatik-AG (NGL)
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
38
README.md
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
# Envoy Client
|
||||||
|
|
||||||
|
<a href="https://github.com/informatik-ag-ngl/envoy-client"><img src="https://raw.githubusercontent.com/informatik-ag-ngl/envoy-client/develop/src/main/resources/icons/envoy_logo.png" align="left" width="200" height="200"></a>
|
||||||
|
|
||||||
|
**Envoy Client** is one of two repositories needed to use the messenger Envoy.<br>
|
||||||
|
The other one is <a href="https://github.com/informatik-ag-ngl/envoy-common">**Envoy Common**</a>.
|
||||||
|
<br><br><br><br><br><br><br><br><br>
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
Envoy Client features a lot of things and many more are yet to come.
|
||||||
|
Currently existing features are:
|
||||||
|
|
||||||
|
* Users
|
||||||
|
* Saving and loading of messages
|
||||||
|
* Login via name
|
||||||
|
* Settings to change the behavior of _Envoy_
|
||||||
|
* UI
|
||||||
|
* Appealing user interface
|
||||||
|
* Changeable themes that store the colors used in _Envoy_
|
||||||
|
* Possibility to run _Envoy_ in the Background once it has been started
|
||||||
|
* Possibility to exit _Envoy_
|
||||||
|
* Connectivity
|
||||||
|
* Sending messages to another person via a predefined server
|
||||||
|
* Offline mode
|
||||||
|
* Programming
|
||||||
|
* API to change default configuration
|
||||||
|
* Advanced logging possibilities
|
||||||
|
* Access without Admin rights possible via local message storage in the home folder
|
||||||
|
* Tons of Events to interact with
|
||||||
|
* Detailed Javadoc to improve readability of code
|
||||||
|
|
||||||
|
## Resources
|
||||||
|
|
||||||
|
* [API Reference (later on)](https://github.com/informatik-ag-ngl/envoy-client/wiki)
|
||||||
|
* [Release Notes](https://github.com/informatik-ag-ngl/envoy-client/releases)
|
||||||
|
* [Gallery (later on)](https://github.com/informatik-ag-ngl/envoy-client/wiki/Gallery)
|
||||||
|
* [Wiki](https://github.com/informatik-ag-ngl/envoy-client/wiki)
|
99
pom.xml
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<groupId>informatik-ag-ngl</groupId>
|
||||||
|
<artifactId>envoy-client</artifactId>
|
||||||
|
<version>0.1-beta</version>
|
||||||
|
|
||||||
|
<name>Envoy Client</name>
|
||||||
|
<url>https://github.com/informatik-ag-ngl/envoy-client</url>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||||
|
<maven.compiler.source>11</maven.compiler.source>
|
||||||
|
<maven.compiler.target>11</maven.compiler.target>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<repositories>
|
||||||
|
<repository>
|
||||||
|
<id>jitpack.io</id>
|
||||||
|
<url>https://jitpack.io</url>
|
||||||
|
</repository>
|
||||||
|
</repositories>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.informatik-ag-ngl</groupId>
|
||||||
|
<artifactId>envoy-common</artifactId>
|
||||||
|
<version>develop-SNAPSHOT</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.openjfx</groupId>
|
||||||
|
<artifactId>javafx-controls</artifactId>
|
||||||
|
<version>11.0.2</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.openjfx</groupId>
|
||||||
|
<artifactId>javafx-fxml</artifactId>
|
||||||
|
<version>11.0.2</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.openjfx</groupId>
|
||||||
|
<artifactId>javafx-graphics </artifactId>
|
||||||
|
<version>11</version>
|
||||||
|
<classifier>win</classifier>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.openjfx</groupId>
|
||||||
|
<artifactId>javafx-graphics </artifactId>
|
||||||
|
<version>11</version>
|
||||||
|
<classifier>linux</classifier>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<finalName>envoy-client</finalName>
|
||||||
|
<resources>
|
||||||
|
<resource>
|
||||||
|
<directory>src/main/resources</directory>
|
||||||
|
</resource>
|
||||||
|
</resources>
|
||||||
|
<pluginManagement>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.8.1</version>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</pluginManagement>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-shade-plugin</artifactId>
|
||||||
|
<version>3.2.4</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>shade</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<shadedArtifactAttached>true</shadedArtifactAttached>
|
||||||
|
<sharedClassifierName>envoy</sharedClassifierName>
|
||||||
|
<transformers>
|
||||||
|
<transformer
|
||||||
|
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||||
|
<mainClass>envoy.client.Main</mainClass>
|
||||||
|
</transformer>
|
||||||
|
</transformers>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
30
src/main/java/envoy/client/Main.java
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
package envoy.client;
|
||||||
|
|
||||||
|
import javafx.application.Application;
|
||||||
|
|
||||||
|
import envoy.client.ui.Startup;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggers application startup.
|
||||||
|
* <p>
|
||||||
|
* To allow Maven shading, the main method has to be separated from the
|
||||||
|
* {@link Startup} class which extends {@link Application}.
|
||||||
|
* <p>
|
||||||
|
* Project: <strong>envoy-client</strong><br>
|
||||||
|
* File: <strong>Main.java</strong><br>
|
||||||
|
* Created: <strong>05.07.2020</strong><br>
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public class Main {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the application.
|
||||||
|
*
|
||||||
|
* @param args the command line arguments are processed by the
|
||||||
|
* client configuration
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public static void main(String[] args) { Application.launch(Startup.class, args); }
|
||||||
|
}
|
65
src/main/java/envoy/client/data/Cache.java
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
package envoy.client.data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.Queue;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import envoy.util.EnvoyLog;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores elements in a queue to process them later.
|
||||||
|
* <p>
|
||||||
|
* Project: <strong>envoy-client</strong><br>
|
||||||
|
* File: <strong>Cache.java</strong><br>
|
||||||
|
* Created: <strong>6 Feb 2020</strong><br>
|
||||||
|
*
|
||||||
|
* @param <T> the type of cached elements
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @since Envoy Client v0.3-alpha
|
||||||
|
*/
|
||||||
|
public final class Cache<T> implements Consumer<T>, Serializable {
|
||||||
|
|
||||||
|
private final Queue<T> elements = new LinkedList<>();
|
||||||
|
private transient Consumer<T> processor;
|
||||||
|
|
||||||
|
private static final Logger logger = EnvoyLog.getLogger(Cache.class);
|
||||||
|
private static final long serialVersionUID = 0L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an element to the cache.
|
||||||
|
*
|
||||||
|
* @param element the element to add
|
||||||
|
* @since Envoy Client v0.3-alpha
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void accept(T element) {
|
||||||
|
logger.log(Level.FINE, String.format("Adding element %s to cache", element));
|
||||||
|
elements.offer(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() { return String.format("Cache[elements=" + elements + "]"); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the processor to which cached elements are relayed.
|
||||||
|
*
|
||||||
|
* @param processor the processor to set
|
||||||
|
* @since Envoy Client v0.3-alpha
|
||||||
|
*/
|
||||||
|
public void setProcessor(Consumer<T> processor) { this.processor = processor; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Relays all cached elements to the processor.
|
||||||
|
*
|
||||||
|
* @throws IllegalStateException if the processor is not initialized
|
||||||
|
* @since Envoy Client v0.3-alpha
|
||||||
|
*/
|
||||||
|
public void relay() {
|
||||||
|
if (processor == null) throw new IllegalStateException("Processor is not defined");
|
||||||
|
elements.forEach(processor::accept);
|
||||||
|
elements.clear();
|
||||||
|
}
|
||||||
|
}
|
66
src/main/java/envoy/client/data/CacheMap.java
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
package envoy.client.data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores a heterogeneous map of {@link Cache} objects with different type
|
||||||
|
* parameters.
|
||||||
|
* <p>
|
||||||
|
* Project: <strong>envoy-client</strong><br>
|
||||||
|
* File: <strong>CacheMap.java</strong><br>
|
||||||
|
* Created: <strong>09.07.2020</strong><br>
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public final class CacheMap implements Serializable {
|
||||||
|
|
||||||
|
private final Map<Class<?>, Cache<?>> map = new HashMap<>();
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a cache to the map.
|
||||||
|
*
|
||||||
|
* @param <T> the type accepted by the cache
|
||||||
|
* @param key the class that maps to the cache
|
||||||
|
* @param cache the cache to store
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public <T> void put(Class<T> key, Cache<T> cache) { map.put(key, cache); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a cache mapped by a class.
|
||||||
|
*
|
||||||
|
* @param <T> the type accepted by the cache
|
||||||
|
* @param key the class that maps to the cache
|
||||||
|
* @return the cache
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
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.
|
||||||
|
*
|
||||||
|
* @param <T> the type accepted by the cache
|
||||||
|
* @param key the class that maps to the cache
|
||||||
|
* @return the cache
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public <T> Cache<? super T> getApplicable(Class<T> key) {
|
||||||
|
Cache<? super T> cache = get(key);
|
||||||
|
if (cache == null)
|
||||||
|
for (var e : map.entrySet())
|
||||||
|
if (e.getKey().isAssignableFrom(key))
|
||||||
|
cache = (Cache<? super T>) e.getValue();
|
||||||
|
return cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the map in which the caches are stored
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public Map<Class<?>, Cache<?>> getMap() { return map; }
|
||||||
|
}
|
153
src/main/java/envoy/client/data/Chat.java
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
package envoy.client.data;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import envoy.client.net.WriteProxy;
|
||||||
|
import envoy.data.*;
|
||||||
|
import envoy.data.Message.MessageStatus;
|
||||||
|
import envoy.event.MessageStatusChange;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a chat between two {@link User}s
|
||||||
|
* as a list of {@link Message} objects.
|
||||||
|
* <p>
|
||||||
|
* Project: <strong>envoy-client</strong><br>
|
||||||
|
* File: <strong>Chat.java</strong><br>
|
||||||
|
* Created: <strong>19 Oct 2019</strong><br>
|
||||||
|
*
|
||||||
|
* @author Maximilian Käfer
|
||||||
|
* @author Leon Hofmeister
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @since Envoy Client v0.1-alpha
|
||||||
|
*/
|
||||||
|
public class Chat implements Serializable {
|
||||||
|
|
||||||
|
protected final Contact recipient;
|
||||||
|
protected final List<Message> messages = new ArrayList<>();
|
||||||
|
|
||||||
|
protected int unreadAmount;
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the list of messages that the recipient receives.
|
||||||
|
* <p>
|
||||||
|
* Saves the Messages in the corresponding chat at that Point.
|
||||||
|
*
|
||||||
|
* @param recipient the user who receives the messages
|
||||||
|
* @since Envoy Client v0.1-alpha
|
||||||
|
*/
|
||||||
|
public Chat(Contact recipient) {
|
||||||
|
this.recipient = recipient;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() { return String.format("Chat[recipient=%s,messages=%d]", recipient, messages.size()); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a hash code based on the recipient.
|
||||||
|
*
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int hashCode() { return Objects.hash(recipient); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests equality to another object based on the recipient.
|
||||||
|
*
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) return true;
|
||||||
|
if (!(obj instanceof Chat)) return false;
|
||||||
|
Chat other = (Chat) obj;
|
||||||
|
return Objects.equals(recipient, other.recipient);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the status of all chat messages received from the recipient to
|
||||||
|
* {@code READ} starting from the bottom and stopping once a read message is
|
||||||
|
* found.
|
||||||
|
*
|
||||||
|
* @param writeProxy the write proxy instance used to notify the server about
|
||||||
|
* the message status changes
|
||||||
|
* @throws IOException if a {@link MessageStatusChange} could not be
|
||||||
|
* delivered to the server
|
||||||
|
* @since Envoy Client v0.3-alpha
|
||||||
|
*/
|
||||||
|
public void read(WriteProxy writeProxy) throws IOException {
|
||||||
|
for (int i = messages.size() - 1; i >= 0; --i) {
|
||||||
|
final Message m = messages.get(i);
|
||||||
|
if (m.getSenderID() == recipient.getID()) if (m.getStatus() == MessageStatus.READ) break;
|
||||||
|
else {
|
||||||
|
m.setStatus(MessageStatus.READ);
|
||||||
|
writeProxy.writeMessageStatusChange(new MessageStatusChange(m));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unreadAmount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {@code true} if the newest message received in the chat doesn't have
|
||||||
|
* the status {@code READ}
|
||||||
|
* @since Envoy Client v0.3-alpha
|
||||||
|
*/
|
||||||
|
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.
|
||||||
|
*
|
||||||
|
* @param message the message to insert
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public void insert(Message message) {
|
||||||
|
for (int i = messages.size() - 1; i >= 0; --i)
|
||||||
|
if (message.getCreationDate().isAfter(messages.get(i).getCreationDate())) {
|
||||||
|
messages.add(i + 1, message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
messages.add(0, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increments the amount of unread messages.
|
||||||
|
*
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public void incrementUnreadAmount() { unreadAmount++; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the amount of unread mesages in this chat
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public int getUnreadAmount() { return unreadAmount; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return all messages in the current chat
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public List<Message> getMessages() { return messages; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the recipient of a message
|
||||||
|
* @since Envoy Client v0.1-alpha
|
||||||
|
*/
|
||||||
|
public Contact getRecipient() { return recipient; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return whether this {@link Chat} points at a {@link User}
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public boolean isUserChat() { return recipient instanceof User; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return whether this {@link Chat} points at a {@link Group}
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public boolean isGroupChat() { return recipient instanceof Group; }
|
||||||
|
}
|
115
src/main/java/envoy/client/data/ClientConfig.java
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
package envoy.client.data;
|
||||||
|
|
||||||
|
import static java.util.function.Function.identity;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
import envoy.client.ui.Startup;
|
||||||
|
import envoy.data.Config;
|
||||||
|
import envoy.data.ConfigItem;
|
||||||
|
import envoy.data.LoginCredentials;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements a configuration specific to the Envoy Client with default values
|
||||||
|
* and convenience methods.
|
||||||
|
* <p>
|
||||||
|
* Project: <strong>envoy-client</strong><br>
|
||||||
|
* File: <strong>ClientConfig.java</strong><br>
|
||||||
|
* Created: <strong>01.03.2020</strong><br>
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public class ClientConfig extends Config {
|
||||||
|
|
||||||
|
private static ClientConfig config;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the singleton instance of the client config
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public static ClientConfig getInstance() {
|
||||||
|
if (config == null) config = new ClientConfig();
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ClientConfig() {
|
||||||
|
items.put("server", new ConfigItem<>("server", "s", identity(), null, true));
|
||||||
|
items.put("port", new ConfigItem<>("port", "p", Integer::parseInt, null, true));
|
||||||
|
items.put("localDB", new ConfigItem<>("localDB", "db", File::new, new File("localDB"), true));
|
||||||
|
items.put("ignoreLocalDB", new ConfigItem<>("ignoreLocalDB", "nodb", Boolean::parseBoolean, false, false));
|
||||||
|
items.put("homeDirectory", new ConfigItem<>("homeDirectory", "h", File::new, new File(System.getProperty("user.home"), ".envoy"), true));
|
||||||
|
items.put("fileLevelBarrier", new ConfigItem<>("fileLevelBarrier", "fb", Level::parse, Level.CONFIG, true));
|
||||||
|
items.put("consoleLevelBarrier", new ConfigItem<>("consoleLevelBarrier", "cb", Level::parse, Level.FINEST, true));
|
||||||
|
items.put("user", new ConfigItem<>("user", "u", identity()));
|
||||||
|
items.put("password", new ConfigItem<>("password", "pw", identity()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the host name of the Envoy server
|
||||||
|
* @since Envoy Client v0.1-alpha
|
||||||
|
*/
|
||||||
|
public String getServer() { return (String) items.get("server").get(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the port at which the Envoy server is located on the host
|
||||||
|
* @since Envoy Client v0.1-alpha
|
||||||
|
*/
|
||||||
|
public Integer getPort() { return (Integer) items.get("port").get(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the local database specific to the client user
|
||||||
|
* @since Envoy Client v0.1-alpha
|
||||||
|
*/
|
||||||
|
public File getLocalDB() { return (File) items.get("localDB").get(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {@code true} if the local database is to be ignored
|
||||||
|
* @since Envoy Client v0.3-alpha
|
||||||
|
*/
|
||||||
|
public Boolean isIgnoreLocalDB() { return (Boolean) items.get("ignoreLocalDB").get(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the directory in which all local files are saves
|
||||||
|
* @since Envoy Client v0.2-alpha
|
||||||
|
*/
|
||||||
|
public File getHomeDirectory() { return (File) items.get("homeDirectory").get(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the minimal {@link Level} to log inside the log file
|
||||||
|
* @since Envoy Client v0.2-alpha
|
||||||
|
*/
|
||||||
|
public Level getFileLevelBarrier() { return (Level) items.get("fileLevelBarrier").get(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the minimal {@link Level} to log inside the console
|
||||||
|
* @since Envoy Client v0.2-alpha
|
||||||
|
*/
|
||||||
|
public Level getConsoleLevelBarrier() { return (Level) items.get("consoleLevelBarrier").get(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the user name
|
||||||
|
* @since Envoy Client v0.3-alpha
|
||||||
|
*/
|
||||||
|
public String getUser() { return (String) items.get("user").get(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the password
|
||||||
|
* @since Envoy Client v0.3-alpha
|
||||||
|
*/
|
||||||
|
public String getPassword() { return (String) items.get("password").get(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {@code true} if user name and password are set
|
||||||
|
* @since Envoy Client v0.3-alpha
|
||||||
|
*/
|
||||||
|
public boolean hasLoginCredentials() { return getUser() != null && getPassword() != null; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return login credentials for the specified user name and password, without
|
||||||
|
* the registration option
|
||||||
|
* @since Envoy Client v0.3-alpha
|
||||||
|
*/
|
||||||
|
public LoginCredentials getLoginCredentials() { return new LoginCredentials(getUser(), getPassword(), false, Startup.VERSION); }
|
||||||
|
}
|
55
src/main/java/envoy/client/data/GroupChat.java
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
package envoy.client.data;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
import envoy.client.net.WriteProxy;
|
||||||
|
import envoy.data.Contact;
|
||||||
|
import envoy.data.GroupMessage;
|
||||||
|
import envoy.data.Message.MessageStatus;
|
||||||
|
import envoy.data.User;
|
||||||
|
import envoy.event.GroupMessageStatusChange;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a chat between a user and a group
|
||||||
|
* as a list of messages.
|
||||||
|
* <p>
|
||||||
|
* Project: <strong>envoy-client</strong><br>
|
||||||
|
* File: <strong>GroupChat.java</strong><br>
|
||||||
|
* Created: <strong>05.07.2020</strong><br>
|
||||||
|
*
|
||||||
|
* @author Maximilian Käfer
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public class GroupChat extends Chat {
|
||||||
|
|
||||||
|
private final User sender;
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param sender the user sending the messages
|
||||||
|
* @param recipient the group whose members receive the messages
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public GroupChat(User sender, Contact recipient) {
|
||||||
|
super(recipient);
|
||||||
|
this.sender = sender;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void read(WriteProxy writeProxy) throws IOException {
|
||||||
|
for (int i = messages.size() - 1; i >= 0; --i) {
|
||||||
|
final GroupMessage gmsg = (GroupMessage) messages.get(i);
|
||||||
|
if (gmsg.getSenderID() != sender.getID()) {
|
||||||
|
if (gmsg.getMemberStatuses().get(sender.getID()) == MessageStatus.READ) break;
|
||||||
|
else {
|
||||||
|
gmsg.getMemberStatuses().replace(sender.getID(), MessageStatus.READ);
|
||||||
|
writeProxy
|
||||||
|
.writeMessageStatusChange(new GroupMessageStatusChange(gmsg.getID(), MessageStatus.READ, LocalDateTime.now(), sender.getID()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unreadAmount = 0;
|
||||||
|
}
|
||||||
|
}
|
205
src/main/java/envoy/client/data/LocalDB.java
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
package envoy.client.data;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import envoy.data.*;
|
||||||
|
import envoy.event.GroupResize;
|
||||||
|
import envoy.event.MessageStatusChange;
|
||||||
|
import envoy.event.NameChange;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores information about the current {@link User} and their {@link Chat}s.
|
||||||
|
* For message ID generation a {@link IDGenerator} is stored as well.
|
||||||
|
* <p>
|
||||||
|
* Project: <strong>envoy-client</strong><br>
|
||||||
|
* File: <strong>LocalDB.java</strong><br>
|
||||||
|
* Created: <strong>3 Feb 2020</strong><br>
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @since Envoy Client v0.3-alpha
|
||||||
|
*/
|
||||||
|
public abstract class LocalDB {
|
||||||
|
|
||||||
|
protected User user;
|
||||||
|
protected Map<String, Contact> users = new HashMap<>();
|
||||||
|
protected List<Chat> chats = new ArrayList<>();
|
||||||
|
protected IDGenerator idGenerator;
|
||||||
|
protected CacheMap cacheMap = new CacheMap();
|
||||||
|
|
||||||
|
{
|
||||||
|
cacheMap.put(Message.class, new Cache<>());
|
||||||
|
cacheMap.put(MessageStatusChange.class, new Cache<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes a storage space for a user-specific list of chats.
|
||||||
|
*
|
||||||
|
* @since Envoy Client v0.3-alpha
|
||||||
|
*/
|
||||||
|
public void initializeUserStorage() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores all users. If the client user is specified, their chats will be stored
|
||||||
|
* as well. The message id generator will also be saved if present.
|
||||||
|
*
|
||||||
|
* @throws Exception if the saving process failed
|
||||||
|
* @since Envoy Client v0.3-alpha
|
||||||
|
*/
|
||||||
|
public void save() throws Exception {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads all user data.
|
||||||
|
*
|
||||||
|
* @throws Exception if the loading process failed
|
||||||
|
* @since Envoy Client v0.3-alpha
|
||||||
|
*/
|
||||||
|
public void loadUsers() throws Exception {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads all data of the client user.
|
||||||
|
*
|
||||||
|
* @throws Exception if the loading process failed
|
||||||
|
* @since Envoy Client v0.3-alpha
|
||||||
|
*/
|
||||||
|
public void loadUserData() throws Exception {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the ID generator. Any exception thrown during this process is ignored.
|
||||||
|
*
|
||||||
|
* @since Envoy Client v0.3-alpha
|
||||||
|
*/
|
||||||
|
public void loadIDGenerator() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synchronizes the contact list of the client user with the chat and user
|
||||||
|
* storage.
|
||||||
|
*
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public void synchronize() {
|
||||||
|
user.getContacts().stream().filter(u -> u instanceof User && !users.containsKey(u.getName())).forEach(u -> users.put(u.getName(), u));
|
||||||
|
users.put(user.getName(), user);
|
||||||
|
|
||||||
|
// Synchronize user status data
|
||||||
|
for (Contact contact : users.values())
|
||||||
|
if (contact instanceof User)
|
||||||
|
getChat(contact.getID()).ifPresent(chat -> { ((User) chat.getRecipient()).setStatus(((User) contact).getStatus()); });
|
||||||
|
|
||||||
|
// Create missing chats
|
||||||
|
user.getContacts()
|
||||||
|
.stream()
|
||||||
|
.filter(c -> !c.equals(user) && getChat(c.getID()).isEmpty())
|
||||||
|
.map(c -> c instanceof User ? new Chat(c) : new GroupChat(user, c))
|
||||||
|
.forEach(chats::add);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return a {@code Map<String, User>} of all users stored locally with their
|
||||||
|
* user names as keys
|
||||||
|
* @since Envoy Client v0.2-alpha
|
||||||
|
*/
|
||||||
|
public Map<String, Contact> getUsers() { return users; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return all saved {@link Chat} objects that list the client user as the
|
||||||
|
* sender
|
||||||
|
* @since Envoy Client v0.1-alpha
|
||||||
|
**/
|
||||||
|
public List<Chat> getChats() { return chats; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param chats the chats to set
|
||||||
|
*/
|
||||||
|
public void setChats(List<Chat> chats) { this.chats = chats; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the {@link User} who initialized the local database
|
||||||
|
* @since Envoy Client v0.2-alpha
|
||||||
|
*/
|
||||||
|
public User getUser() { return user; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param user the user to set
|
||||||
|
* @since Envoy Client v0.2-alpha
|
||||||
|
*/
|
||||||
|
public void setUser(User user) { this.user = user; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the message ID generator
|
||||||
|
* @since Envoy Client v0.3-alpha
|
||||||
|
*/
|
||||||
|
public IDGenerator getIDGenerator() { return idGenerator; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param idGenerator the message ID generator to set
|
||||||
|
* @since Envoy Client v0.3-alpha
|
||||||
|
*/
|
||||||
|
public void setIDGenerator(IDGenerator idGenerator) { this.idGenerator = idGenerator; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {@code true} if an {@link IDGenerator} is present
|
||||||
|
* @since Envoy Client v0.3-alpha
|
||||||
|
*/
|
||||||
|
public boolean hasIDGenerator() { return idGenerator != null; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the cache map for messages and message status changes
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public CacheMap getCacheMap() { return cacheMap; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Searches for a message by ID.
|
||||||
|
*
|
||||||
|
* @param id the ID of the message to search for
|
||||||
|
* @return an optional containing the message
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public Optional<Message> getMessage(long id) {
|
||||||
|
return chats.stream().map(Chat::getMessages).flatMap(List::stream).filter(m -> m.getID() == id).findAny();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Searches for a chat by recipient ID.
|
||||||
|
*
|
||||||
|
* @param recipientID the ID of the chat's recipient
|
||||||
|
* @return an optional containing the chat
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public Optional<Chat> getChat(long recipientID) { return chats.stream().filter(c -> c.getRecipient().getID() == recipientID).findAny(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a contact name change if the corresponding contact is present.
|
||||||
|
*
|
||||||
|
* @param event the {@link NameChange} to process
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public void replaceContactName(NameChange event) {
|
||||||
|
chats.stream().map(Chat::getRecipient).filter(c -> c.getID() == event.getID()).findAny().ifPresent(c -> c.setName(event.get()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a group resize operation if the corresponding group is present.
|
||||||
|
*
|
||||||
|
* @param event the {@link GroupResize} to process
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public void updateGroup(GroupResize event) {
|
||||||
|
chats.stream()
|
||||||
|
.map(Chat::getRecipient)
|
||||||
|
.filter(Group.class::isInstance)
|
||||||
|
.filter(g -> g.getID() == event.getGroupID() && g.getID() != user.getID())
|
||||||
|
.map(Group.class::cast)
|
||||||
|
.findAny()
|
||||||
|
.ifPresent(group -> {
|
||||||
|
switch (event.getOperation()) {
|
||||||
|
case ADD:
|
||||||
|
group.getContacts().add(event.get());
|
||||||
|
break;
|
||||||
|
case REMOVE:
|
||||||
|
group.getContacts().remove(event.get());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
88
src/main/java/envoy/client/data/PersistentLocalDB.java
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
package envoy.client.data;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
import envoy.data.IDGenerator;
|
||||||
|
import envoy.util.SerializationUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements a {@link LocalDB} in a way that stores all information inside a
|
||||||
|
* folder on the local file system.
|
||||||
|
* <p>
|
||||||
|
* Project: <strong>envoy-client</strong><br>
|
||||||
|
* File: <strong>PersistentLocalDB.java</strong><br>
|
||||||
|
* Created: <strong>27.10.2019</strong><br>
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @author Maximilian Käfer
|
||||||
|
* @since Envoy Client v0.1-alpha
|
||||||
|
*/
|
||||||
|
public final class PersistentLocalDB extends LocalDB {
|
||||||
|
|
||||||
|
private File dbDir, userFile, idGeneratorFile, usersFile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an empty local database. To serialize any user-specific data to
|
||||||
|
* the file system, call {@link PersistentLocalDB#initializeUserStorage()} first
|
||||||
|
* and then {@link PersistentLocalDB#save()}.
|
||||||
|
*
|
||||||
|
* @param dbDir the directory in which to persist data
|
||||||
|
* @throws IOException if {@code dbDir} is a file (and not a directory)
|
||||||
|
* @since Envoy Client v0.1-alpha
|
||||||
|
*/
|
||||||
|
public PersistentLocalDB(File dbDir) throws IOException {
|
||||||
|
this.dbDir = dbDir;
|
||||||
|
|
||||||
|
// Test if the database directory is actually a directory
|
||||||
|
if (dbDir.exists() && !dbDir.isDirectory())
|
||||||
|
throw new IOException(String.format("LocalDBDir '%s' is not a directory!", dbDir.getAbsolutePath()));
|
||||||
|
|
||||||
|
// Initialize global files
|
||||||
|
idGeneratorFile = new File(dbDir, "id_gen.db");
|
||||||
|
usersFile = new File(dbDir, "users.db");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a database file for a user-specific list of chats.
|
||||||
|
*
|
||||||
|
* @throws IllegalStateException if the client user is not specified
|
||||||
|
* @since Envoy Client v0.1-alpha
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void initializeUserStorage() {
|
||||||
|
if (user == null) throw new IllegalStateException("Client user is null, cannot initialize user storage");
|
||||||
|
userFile = new File(dbDir, user.getID() + ".db");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void save() throws IOException {
|
||||||
|
// Save users
|
||||||
|
SerializationUtils.write(usersFile, users);
|
||||||
|
|
||||||
|
// Save user data
|
||||||
|
if (user != null) SerializationUtils.write(userFile, chats, cacheMap);
|
||||||
|
|
||||||
|
// Save id generator
|
||||||
|
if (hasIDGenerator()) SerializationUtils.write(idGeneratorFile, idGenerator);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void loadUsers() throws ClassNotFoundException, IOException { users = SerializationUtils.read(usersFile, HashMap.class); }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void loadUserData() throws ClassNotFoundException, IOException {
|
||||||
|
try (var in = new ObjectInputStream(new FileInputStream(userFile))) {
|
||||||
|
chats = (ArrayList<Chat>) in.readObject();
|
||||||
|
cacheMap = (CacheMap) in.readObject();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void loadIDGenerator() {
|
||||||
|
try {
|
||||||
|
idGenerator = SerializationUtils.read(idGeneratorFile, IDGenerator.class);
|
||||||
|
} catch (ClassNotFoundException | IOException e) {}
|
||||||
|
}
|
||||||
|
}
|
146
src/main/java/envoy/client/data/Settings.java
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
package envoy.client.data;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.prefs.Preferences;
|
||||||
|
|
||||||
|
import envoy.util.SerializationUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages all application settings, which are different objects that can be
|
||||||
|
* changed during runtime and serialized them by using either the file system or
|
||||||
|
* the {@link Preferences} API.
|
||||||
|
* <p>
|
||||||
|
* Project: <strong>envoy-client</strong><br>
|
||||||
|
* File: <strong>Settings.java</strong><br>
|
||||||
|
* Created: <strong>11 Nov 2019</strong><br>
|
||||||
|
*
|
||||||
|
* @author Leon Hofmeister
|
||||||
|
* @author Maximilian Käfer
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @since Envoy Client v0.2-alpha
|
||||||
|
*/
|
||||||
|
public class Settings {
|
||||||
|
|
||||||
|
// Actual settings accessible by the rest of the application
|
||||||
|
private Map<String, SettingsItem<?>> items;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Settings are stored in this file.
|
||||||
|
*/
|
||||||
|
private static final File settingsFile = new File(ClientConfig.getInstance().getHomeDirectory(), "settings.ser");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Singleton instance of this class.
|
||||||
|
*/
|
||||||
|
private static Settings settings = new Settings();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The way to instantiate the settings. Is set to private to deny other
|
||||||
|
* instances of that object.
|
||||||
|
*
|
||||||
|
* @since Envoy Client v0.2-alpha
|
||||||
|
*/
|
||||||
|
private Settings() {
|
||||||
|
// Load settings from settings file
|
||||||
|
try {
|
||||||
|
items = SerializationUtils.read(settingsFile, HashMap.class);
|
||||||
|
} catch (ClassNotFoundException | IOException e) {
|
||||||
|
items = new HashMap<>();
|
||||||
|
}
|
||||||
|
supplementDefaults();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is used to ensure that there is only one instance of Settings.
|
||||||
|
*
|
||||||
|
* @return the instance of Settings
|
||||||
|
* @since Envoy Client v0.2-alpha
|
||||||
|
*/
|
||||||
|
public static Settings getInstance() { return settings; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the preferences when the save button is clicked.
|
||||||
|
*
|
||||||
|
* @throws IOException if an error occurs while saving the themes
|
||||||
|
* @since Envoy Client v0.2-alpha
|
||||||
|
*/
|
||||||
|
public void save() throws IOException {
|
||||||
|
|
||||||
|
// Save settings to settings file
|
||||||
|
SerializationUtils.write(settingsFile, items);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void supplementDefaults() {
|
||||||
|
items.putIfAbsent("enterToSend", new SettingsItem<>(true, "Enter to send", "Sends a message by pressing the enter key."));
|
||||||
|
items.putIfAbsent("onCloseMode", new SettingsItem<>(true, "Hide on close", "Hides the chat window when it is closed."));
|
||||||
|
items.putIfAbsent("currentTheme", new SettingsItem<>("dark", "Current Theme Name", "The name of the currently selected theme."));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the name of the currently active theme
|
||||||
|
* @since Envoy Client v0.2-alpha
|
||||||
|
*/
|
||||||
|
public String getCurrentTheme() { return (String) items.get("currentTheme").get(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the name of the current theme.
|
||||||
|
*
|
||||||
|
* @param themeName the name to set
|
||||||
|
* @since Envoy Client v0.2-alpha
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public boolean isUsingDefaultTheme() {
|
||||||
|
final var theme = getCurrentTheme();
|
||||||
|
return theme.equals("dark") || theme.equals("light");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {@code true}, if pressing the {@code Enter} key suffices to send a
|
||||||
|
* message. Otherwise it has to be pressed in conjunction with the
|
||||||
|
* {@code Control} key.
|
||||||
|
* @since Envoy Client v0.2-alpha
|
||||||
|
*/
|
||||||
|
public Boolean isEnterToSend() { return (Boolean) items.get("enterToSend").get(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
* the {@code Enter} key. Otherwise it has to be pressed in
|
||||||
|
* conjunction with the {@code Control} key.
|
||||||
|
* @since Envoy Client v0.2-alpha
|
||||||
|
*/
|
||||||
|
public void setEnterToSend(boolean enterToSend) { ((SettingsItem<Boolean>) items.get("enterToSend")).set(enterToSend); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the current on close mode.
|
||||||
|
* @since Envoy Client v0.3-alpha
|
||||||
|
*/
|
||||||
|
public Boolean getCurrentOnCloseMode() { return (Boolean) items.get("onCloseMode").get(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the current on close mode.
|
||||||
|
*
|
||||||
|
* @param currentOnCloseMode the on close mode that should be set.
|
||||||
|
* @since Envoy Client v0.3-alpha
|
||||||
|
*/
|
||||||
|
public void setCurrentOnCloseMode(boolean currentOnCloseMode) { ((SettingsItem<Boolean>) items.get("onCloseMode")).set(currentOnCloseMode); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the items
|
||||||
|
*/
|
||||||
|
public Map<String, SettingsItem<?>> getItems() { return items; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param items the items to set
|
||||||
|
*/
|
||||||
|
public void setItems(Map<String, SettingsItem<?>> items) { this.items = items; }
|
||||||
|
}
|
99
src/main/java/envoy/client/data/SettingsItem.java
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
package envoy.client.data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import javax.swing.JComponent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encapsulates a persistent value that is directly or indirectly mutable by the
|
||||||
|
* user.
|
||||||
|
* <p>
|
||||||
|
* Project: <strong>envoy-client</strong><br>
|
||||||
|
* File: <strong>SettingsItem.java</strong><br>
|
||||||
|
* Created: <strong>23.12.2019</strong><br>
|
||||||
|
*
|
||||||
|
* @param <T> the type of this {@link SettingsItem}'s value
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @since Envoy Client v0.3-alpha
|
||||||
|
*/
|
||||||
|
public class SettingsItem<T> implements Serializable {
|
||||||
|
|
||||||
|
private T value;
|
||||||
|
private String userFriendlyName, description;
|
||||||
|
|
||||||
|
private transient Consumer<T> changeHandler;
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes a {@link SettingsItem}. The default value's class will be mapped
|
||||||
|
* to a {@link JComponent} that can be used to display this {@link SettingsItem}
|
||||||
|
* to the user.
|
||||||
|
*
|
||||||
|
* @param value the default value
|
||||||
|
* @param userFriendlyName the user friendly name (short)
|
||||||
|
* @param description the description (long)
|
||||||
|
* @since Envoy Client v0.3-alpha
|
||||||
|
*/
|
||||||
|
public SettingsItem(T value, String userFriendlyName, String description) {
|
||||||
|
this.value = value;
|
||||||
|
this.userFriendlyName = userFriendlyName;
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the value
|
||||||
|
* @since Envoy Client v0.3-alpha
|
||||||
|
*/
|
||||||
|
public T get() { return value; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the value of this {@link SettingsItem}. If a {@code ChangeHandler} if
|
||||||
|
* defined, it will be invoked with this value.
|
||||||
|
*
|
||||||
|
* @param value the value to set
|
||||||
|
* @since Envoy Client v0.3-alpha
|
||||||
|
*/
|
||||||
|
public void set(T value) {
|
||||||
|
if (changeHandler != null && value != this.value) changeHandler.accept(value);
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the userFriendlyName
|
||||||
|
* @since Envoy Client v0.3-alpha
|
||||||
|
*/
|
||||||
|
public String getUserFriendlyName() { return userFriendlyName; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param userFriendlyName the userFriendlyName to set
|
||||||
|
* @since Envoy Client v0.3-alpha
|
||||||
|
*/
|
||||||
|
public void setUserFriendlyName(String userFriendlyName) { this.userFriendlyName = userFriendlyName; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the description
|
||||||
|
* @since Envoy Client v0.3-alpha
|
||||||
|
*/
|
||||||
|
public String getDescription() { return description; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param description the description to set
|
||||||
|
* @since Envoy Client v0.3-alpha
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
15
src/main/java/envoy/client/data/TransientLocalDB.java
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package envoy.client.data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements a {@link LocalDB} in a way that does not persist any information
|
||||||
|
* after application shutdown.
|
||||||
|
* <p>
|
||||||
|
* Project: <strong>envoy-client</strong><br>
|
||||||
|
* File: <strong>TransientLocalDB.java</strong><br>
|
||||||
|
* Created: <strong>3 Feb 2020</strong><br>
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @since Envoy Client v0.3-alpha
|
||||||
|
*/
|
||||||
|
public final class TransientLocalDB extends LocalDB {
|
||||||
|
}
|
64
src/main/java/envoy/client/data/audio/AudioPlayer.java
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
package envoy.client.data.audio;
|
||||||
|
|
||||||
|
import javax.sound.sampled.*;
|
||||||
|
|
||||||
|
import envoy.exception.EnvoyException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plays back audio from a byte array.
|
||||||
|
* <p>
|
||||||
|
* Project: <strong>envoy-client</strong><br>
|
||||||
|
* File: <strong>AudioPlayer.java</strong><br>
|
||||||
|
* Created: <strong>05.07.2020</strong><br>
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public final class AudioPlayer {
|
||||||
|
|
||||||
|
private final AudioFormat format;
|
||||||
|
private final DataLine.Info info;
|
||||||
|
|
||||||
|
private Clip clip;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the player with the default audio format.
|
||||||
|
*
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public AudioPlayer() { this(AudioRecorder.DEFAULT_AUDIO_FORMAT); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the player with a given audio format.
|
||||||
|
*
|
||||||
|
* @param format the audio format to use
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public AudioPlayer(AudioFormat format) {
|
||||||
|
this.format = format;
|
||||||
|
info = new DataLine.Info(Clip.class, format);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {@code true} if audio play back is supported
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public boolean isSupported() { return AudioSystem.isLineSupported(info); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plays back an audio clip.
|
||||||
|
*
|
||||||
|
* @param data the data of the clip
|
||||||
|
* @throws EnvoyException if the play back failed
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public void play(byte[] data) throws EnvoyException {
|
||||||
|
try {
|
||||||
|
clip = (Clip) AudioSystem.getLine(info);
|
||||||
|
clip.open(format, data, 0, data.length);
|
||||||
|
clip.start();
|
||||||
|
} catch (final LineUnavailableException e) {
|
||||||
|
throw new EnvoyException("Cannot play back audio", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
122
src/main/java/envoy/client/data/audio/AudioRecorder.java
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
package envoy.client.data.audio;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
import javax.sound.sampled.*;
|
||||||
|
|
||||||
|
import envoy.exception.EnvoyException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Records audio and exports it as a byte array.
|
||||||
|
* <p>
|
||||||
|
* Project: <strong>envoy-client</strong><br>
|
||||||
|
* File: <strong>AudioRecorder.java</strong><br>
|
||||||
|
* Created: <strong>02.07.2020</strong><br>
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public final class AudioRecorder {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default audio format used for recording and play back.
|
||||||
|
*
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public static final AudioFormat DEFAULT_AUDIO_FORMAT = new AudioFormat(16000, 16, 1, true, false);
|
||||||
|
|
||||||
|
private final AudioFormat format;
|
||||||
|
private final DataLine.Info info;
|
||||||
|
|
||||||
|
private TargetDataLine line;
|
||||||
|
private Path tempFile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the recorder with the default audio format.
|
||||||
|
*
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public AudioRecorder() { this(DEFAULT_AUDIO_FORMAT); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the recorder with a given audio format.
|
||||||
|
*
|
||||||
|
* @param format the audio format to use
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public AudioRecorder(AudioFormat format) {
|
||||||
|
this.format = format;
|
||||||
|
info = new DataLine.Info(TargetDataLine.class, format);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {@code true} if audio recording is supported
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public boolean isSupported() { return AudioSystem.isLineSupported(info); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {@code true} if the recorder is active
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public boolean isRecording() { return line != null && line.isActive(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the audio recording.
|
||||||
|
*
|
||||||
|
* @throws EnvoyException if starting the recording failed
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public void start() throws EnvoyException {
|
||||||
|
try {
|
||||||
|
|
||||||
|
// Open the line
|
||||||
|
line = (TargetDataLine) AudioSystem.getLine(info);
|
||||||
|
line.open(format);
|
||||||
|
line.start();
|
||||||
|
|
||||||
|
// Prepare temp file
|
||||||
|
tempFile = Files.createTempFile("recording", "wav");
|
||||||
|
|
||||||
|
// Start the recording
|
||||||
|
final var ais = new AudioInputStream(line);
|
||||||
|
AudioSystem.write(ais, AudioFileFormat.Type.WAVE, tempFile.toFile());
|
||||||
|
} catch (IOException | LineUnavailableException e) {
|
||||||
|
throw new EnvoyException("Cannot record voice", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops the recording.
|
||||||
|
*
|
||||||
|
* @return the finished recording
|
||||||
|
* @throws EnvoyException if finishing the recording failed
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public byte[] finish() throws EnvoyException {
|
||||||
|
try {
|
||||||
|
line.stop();
|
||||||
|
line.close();
|
||||||
|
final byte[] data = Files.readAllBytes(tempFile);
|
||||||
|
Files.delete(tempFile);
|
||||||
|
return data;
|
||||||
|
} catch (final IOException e) {
|
||||||
|
throw new EnvoyException("Cannot save voice recording", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancels the active recording.
|
||||||
|
*
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public void cancel() {
|
||||||
|
line.stop();
|
||||||
|
line.close();
|
||||||
|
try {
|
||||||
|
Files.deleteIfExists(tempFile);
|
||||||
|
} catch (IOException e) {}
|
||||||
|
}
|
||||||
|
}
|
11
src/main/java/envoy/client/data/audio/package-info.java
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
/**
|
||||||
|
* Contains classes related to recording and playing back audio clips.
|
||||||
|
* <p>
|
||||||
|
* Project: <strong>envoy-client</strong><br>
|
||||||
|
* File: <strong>package-info.java</strong><br>
|
||||||
|
* Created: <strong>05.07.2020</strong><br>
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
package envoy.client.data.audio;
|
9
src/main/java/envoy/client/data/package-info.java
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
/**
|
||||||
|
* This package contains all data classes and classes related to persistence.
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @author Leon Hofmeister
|
||||||
|
* @author Maximilian Käfer
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
package envoy.client.data;
|
22
src/main/java/envoy/client/event/MessageCreationEvent.java
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package envoy.client.event;
|
||||||
|
|
||||||
|
import envoy.data.Message;
|
||||||
|
import envoy.event.Event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Project: <strong>envoy-client</strong><br>
|
||||||
|
* File: <strong>MessageCreationEvent.java</strong><br>
|
||||||
|
* Created: <strong>4 Dec 2019</strong><br>
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @since Envoy Client v0.2-alpha
|
||||||
|
*/
|
||||||
|
public class MessageCreationEvent extends Event<Message> {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 0L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param message the {@link Message} that has been created
|
||||||
|
*/
|
||||||
|
public MessageCreationEvent(Message message) { super(message); }
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
package envoy.client.event;
|
||||||
|
|
||||||
|
import envoy.data.Message;
|
||||||
|
import envoy.event.Event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Project: <strong>envoy-client</strong><br>
|
||||||
|
* File: <strong>MessageModificationEvent.java</strong><br>
|
||||||
|
* Created: <strong>4 Dec 2019</strong><br>
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @since Envoy Client v0.2-alpha
|
||||||
|
*/
|
||||||
|
public class MessageModificationEvent extends Event<Message> {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 0L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param message the {@link Message} that has been modified
|
||||||
|
*/
|
||||||
|
public MessageModificationEvent(Message message) { super(message); }
|
||||||
|
}
|
22
src/main/java/envoy/client/event/SendEvent.java
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package envoy.client.event;
|
||||||
|
|
||||||
|
import envoy.event.Event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Project: <strong>envoy-client</strong><br>
|
||||||
|
* File: <strong>SendEvent.java</strong><br>
|
||||||
|
* Created: <strong>11.02.2020</strong><br>
|
||||||
|
*
|
||||||
|
* @author: Maximilian Käfer
|
||||||
|
* @since Envoy Client v0.3-alpha
|
||||||
|
*/
|
||||||
|
public class SendEvent extends Event<Event<?>> {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 0L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param value the event to send to the server
|
||||||
|
*/
|
||||||
|
public SendEvent(Event<?> value) { super(value); }
|
||||||
|
|
||||||
|
}
|
25
src/main/java/envoy/client/event/ThemeChangeEvent.java
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
package envoy.client.event;
|
||||||
|
|
||||||
|
import envoy.event.Event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Project: <strong>envoy-client</strong><br>
|
||||||
|
* File: <strong>ThemeChangeEvent.java</strong><br>
|
||||||
|
* Created: <strong>15 Dec 2019</strong><br>
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @since Envoy Client v0.2-alpha
|
||||||
|
*/
|
||||||
|
public class ThemeChangeEvent extends Event<String> {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 0L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes a {@link ThemeChangeEvent} conveying information about the change
|
||||||
|
* of the theme currently in use.
|
||||||
|
*
|
||||||
|
* @param theme the name of the new theme
|
||||||
|
* @since Envoy Client v0.2-alpha
|
||||||
|
*/
|
||||||
|
public ThemeChangeEvent(String theme) { super(theme); }
|
||||||
|
}
|
9
src/main/java/envoy/client/event/package-info.java
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
/**
|
||||||
|
* This package contains all client-sided events.
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @author Leon Hofmeister
|
||||||
|
* @author Maximilian Käfer
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
package envoy.client.event;
|
241
src/main/java/envoy/client/net/Client.java
Normal file
@ -0,0 +1,241 @@
|
|||||||
|
package envoy.client.net;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import envoy.client.data.*;
|
||||||
|
import envoy.client.event.SendEvent;
|
||||||
|
import envoy.data.*;
|
||||||
|
import envoy.event.*;
|
||||||
|
import envoy.event.contact.ContactOperation;
|
||||||
|
import envoy.event.contact.ContactSearchResult;
|
||||||
|
import envoy.util.EnvoyLog;
|
||||||
|
import envoy.util.SerializationUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Establishes a connection to the server, performs a handshake and delivers
|
||||||
|
* certain objects to the server.
|
||||||
|
* <p>
|
||||||
|
* Project: <strong>envoy-client</strong><br>
|
||||||
|
* File: <strong>Client.java</strong><br>
|
||||||
|
* Created: <strong>28 Sep 2019</strong><br>
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @author Maximilian Käfer
|
||||||
|
* @author Leon Hofmeister
|
||||||
|
* @since Envoy Client v0.1-alpha
|
||||||
|
*/
|
||||||
|
public class Client implements Closeable {
|
||||||
|
|
||||||
|
// Connection handling
|
||||||
|
private Socket socket;
|
||||||
|
private Receiver receiver;
|
||||||
|
private boolean online;
|
||||||
|
|
||||||
|
// Asynchronously initialized during handshake
|
||||||
|
private volatile User sender;
|
||||||
|
private volatile boolean rejected;
|
||||||
|
|
||||||
|
// Configuration, logging and event management
|
||||||
|
private static final ClientConfig config = ClientConfig.getInstance();
|
||||||
|
private static final Logger logger = EnvoyLog.getLogger(Client.class);
|
||||||
|
private static final EventBus eventBus = EventBus.getInstance();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enters the online mode by acquiring a user ID from the server. As a
|
||||||
|
* connection has to be established and a handshake has to be made, this method
|
||||||
|
* will block for up to 5 seconds. If the handshake does exceed this time limit,
|
||||||
|
* an exception is thrown.
|
||||||
|
*
|
||||||
|
* @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 IOException if the login credentials could not be written
|
||||||
|
* @throws InterruptedException if the current thread is interrupted while
|
||||||
|
* waiting for the handshake response
|
||||||
|
*/
|
||||||
|
public void performHandshake(LoginCredentials credentials, CacheMap cacheMap) throws TimeoutException, IOException, InterruptedException {
|
||||||
|
if (online) throw new IllegalStateException("Handshake has already been performed successfully");
|
||||||
|
|
||||||
|
// Establish TCP connection
|
||||||
|
logger.log(Level.FINER, String.format("Attempting connection to server %s:%d...", config.getServer(), config.getPort()));
|
||||||
|
socket = new Socket(config.getServer(), config.getPort());
|
||||||
|
logger.log(Level.FINE, "Successfully established TCP connection to server");
|
||||||
|
|
||||||
|
// Create object receiver
|
||||||
|
receiver = new Receiver(socket.getInputStream());
|
||||||
|
|
||||||
|
// Register user creation processor, contact list processor and message cache
|
||||||
|
receiver.registerProcessor(User.class, sender -> this.sender = sender);
|
||||||
|
receiver.registerProcessors(cacheMap.getMap());
|
||||||
|
receiver.registerProcessor(HandshakeRejection.class, evt -> { rejected = true; eventBus.dispatch(evt); });
|
||||||
|
|
||||||
|
rejected = false;
|
||||||
|
|
||||||
|
// Start receiver
|
||||||
|
receiver.start();
|
||||||
|
|
||||||
|
// Write login credentials
|
||||||
|
SerializationUtils.writeBytesWithLength(credentials, socket.getOutputStream());
|
||||||
|
|
||||||
|
// Wait for a maximum of five seconds to acquire the sender object
|
||||||
|
final long start = System.currentTimeMillis();
|
||||||
|
while (sender == null) {
|
||||||
|
|
||||||
|
// Quit immediately after handshake rejection
|
||||||
|
// This method can then be called again
|
||||||
|
if (rejected) {
|
||||||
|
socket.close();
|
||||||
|
receiver.removeAllProcessors();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (System.currentTimeMillis() - start > 5000) throw new TimeoutException("Did not log in after 5 seconds");
|
||||||
|
Thread.sleep(500);
|
||||||
|
}
|
||||||
|
|
||||||
|
online = true;
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
// Process incoming messages
|
||||||
|
final var receivedMessageProcessor = new ReceivedMessageProcessor();
|
||||||
|
final var receivedGroupMessageProcessor = new ReceivedGroupMessageProcessor();
|
||||||
|
final var messageStatusChangeProcessor = new MessageStatusChangeProcessor();
|
||||||
|
final var groupMessageStatusChangeProcessor = new GroupMessageStatusChangeProcessor();
|
||||||
|
|
||||||
|
receiver.registerProcessor(GroupMessage.class, receivedGroupMessageProcessor);
|
||||||
|
receiver.registerProcessor(Message.class, receivedMessageProcessor);
|
||||||
|
receiver.registerProcessor(MessageStatusChange.class, messageStatusChangeProcessor);
|
||||||
|
receiver.registerProcessor(GroupMessageStatusChange.class, groupMessageStatusChangeProcessor);
|
||||||
|
|
||||||
|
// Relay cached messages and message status changes
|
||||||
|
cacheMap.get(Message.class).setProcessor(receivedMessageProcessor);
|
||||||
|
cacheMap.get(GroupMessage.class).setProcessor(receivedGroupMessageProcessor);
|
||||||
|
cacheMap.get(MessageStatusChange.class).setProcessor(messageStatusChangeProcessor);
|
||||||
|
cacheMap.get(GroupMessageStatusChange.class).setProcessor(groupMessageStatusChangeProcessor);
|
||||||
|
|
||||||
|
// Process user status changes
|
||||||
|
receiver.registerProcessor(UserStatusChange.class, eventBus::dispatch);
|
||||||
|
|
||||||
|
// Process message ID generation
|
||||||
|
receiver.registerProcessor(IDGenerator.class, localDB::setIDGenerator);
|
||||||
|
|
||||||
|
// Process name changes
|
||||||
|
receiver.registerProcessor(NameChange.class, evt -> { localDB.replaceContactName(evt); eventBus.dispatch(evt); });
|
||||||
|
|
||||||
|
// Process contact searches
|
||||||
|
receiver.registerProcessor(ContactSearchResult.class, eventBus::dispatch);
|
||||||
|
|
||||||
|
// Process contact operations
|
||||||
|
receiver.registerProcessor(ContactOperation.class, eventBus::dispatch);
|
||||||
|
|
||||||
|
// Process group size changes
|
||||||
|
receiver.registerProcessor(GroupResize.class, evt -> { localDB.updateGroup(evt); eventBus.dispatch(evt); });
|
||||||
|
|
||||||
|
// Send event
|
||||||
|
eventBus.register(SendEvent.class, evt -> {
|
||||||
|
try {
|
||||||
|
sendEvent(evt.get());
|
||||||
|
} catch (final IOException e) {
|
||||||
|
logger.log(Level.WARNING, "An error occurred when trying to send " + evt, e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a message to the server. The message's status will be incremented once
|
||||||
|
* it was delivered successfully.
|
||||||
|
*
|
||||||
|
* @param message the message to send
|
||||||
|
* @throws IOException if the message does not reach the server
|
||||||
|
* @since Envoy Client v0.3-alpha
|
||||||
|
*/
|
||||||
|
public void sendMessage(Message message) throws IOException {
|
||||||
|
writeObject(message);
|
||||||
|
message.nextStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends an event to the server.
|
||||||
|
*
|
||||||
|
* @param evt the event to send
|
||||||
|
* @throws IOException if the event did not reach the server
|
||||||
|
*/
|
||||||
|
public void sendEvent(Event<?> evt) throws IOException { writeObject(evt); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requests a new {@link IDGenerator} from the server.
|
||||||
|
*
|
||||||
|
* @throws IOException if the request does not reach the server
|
||||||
|
* @since Envoy Client v0.3-alpha
|
||||||
|
*/
|
||||||
|
public void requestIdGenerator() throws IOException {
|
||||||
|
logger.log(Level.INFO, "Requesting new id generator...");
|
||||||
|
writeObject(new IDGeneratorRequest());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException { if (online) socket.close(); }
|
||||||
|
|
||||||
|
private void writeObject(Object obj) throws IOException {
|
||||||
|
checkOnline();
|
||||||
|
logger.log(Level.FINE, "Sending " + obj);
|
||||||
|
SerializationUtils.writeBytesWithLength(obj, socket.getOutputStream());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkOnline() { if (!online) throw new IllegalStateException("Client is not online"); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the {@link User} as which this client is logged in
|
||||||
|
* @since Envoy Client v0.1-alpha
|
||||||
|
*/
|
||||||
|
public User getSender() { return sender; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the client user which is used to send messages.
|
||||||
|
*
|
||||||
|
* @param clientUser the client user to set
|
||||||
|
* @since Envoy Client v0.2-alpha
|
||||||
|
*/
|
||||||
|
public void setSender(User clientUser) { sender = clientUser; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the {@link Receiver} used by this {@link Client}
|
||||||
|
*/
|
||||||
|
public Receiver getReceiver() { return receiver; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {@code true} if a connection to the server could be established
|
||||||
|
* @since Envoy Client v0.2-alpha
|
||||||
|
*/
|
||||||
|
public boolean isOnline() { return online; }
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
package envoy.client.net;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import envoy.data.Message.MessageStatus;
|
||||||
|
import envoy.event.EventBus;
|
||||||
|
import envoy.event.GroupMessageStatusChange;
|
||||||
|
import envoy.util.EnvoyLog;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Project: <strong>envoy-client</strong><br>
|
||||||
|
* File: <strong>GroupMessageStatusChangePocessor.java</strong><br>
|
||||||
|
* Created: <strong>03.07.2020</strong><br>
|
||||||
|
*
|
||||||
|
* @author Maximilian Käfer
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public class GroupMessageStatusChangeProcessor implements Consumer<GroupMessageStatusChange> {
|
||||||
|
|
||||||
|
private static final Logger logger = EnvoyLog.getLogger(GroupMessageStatusChangeProcessor.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void accept(GroupMessageStatusChange evt) {
|
||||||
|
if (evt.get().ordinal() < MessageStatus.RECEIVED.ordinal()) logger.warning("Received invalid group message status change " + evt);
|
||||||
|
else EventBus.getInstance().dispatch(evt);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
package envoy.client.net;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import envoy.data.Message.MessageStatus;
|
||||||
|
import envoy.event.EventBus;
|
||||||
|
import envoy.event.MessageStatusChange;
|
||||||
|
import envoy.util.EnvoyLog;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Project: <strong>envoy-client</strong><br>
|
||||||
|
* File: <strong>MessageStatusChangeProcessor.java</strong><br>
|
||||||
|
* Created: <strong>4 Feb 2020</strong><br>
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @since Envoy Client v0.3-alpha
|
||||||
|
*/
|
||||||
|
public class MessageStatusChangeProcessor implements Consumer<MessageStatusChange> {
|
||||||
|
|
||||||
|
private static final Logger logger = EnvoyLog.getLogger(MessageStatusChangeProcessor.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatches a {@link MessageStatusChange} if the status is
|
||||||
|
* {@code RECEIVED} or {@code READ}.
|
||||||
|
*
|
||||||
|
* @param evt the status change event
|
||||||
|
* @since Envoy Client v0.3-alpha
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void accept(MessageStatusChange evt) {
|
||||||
|
if (evt.get().ordinal() < MessageStatus.RECEIVED.ordinal()) logger.warning("Received invalid message status change " + evt);
|
||||||
|
else EventBus.getInstance().dispatch(evt);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
package envoy.client.net;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import envoy.client.event.MessageCreationEvent;
|
||||||
|
import envoy.data.GroupMessage;
|
||||||
|
import envoy.data.Message.MessageStatus;
|
||||||
|
import envoy.event.EventBus;
|
||||||
|
import envoy.util.EnvoyLog;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Project: <strong>envoy-client</strong><br>
|
||||||
|
* File: <strong>ReceivedGroupMessageProcessor.java</strong><br>
|
||||||
|
* Created: <strong>13.06.2020</strong><br>
|
||||||
|
*
|
||||||
|
* @author Maximilian Käfer
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public class ReceivedGroupMessageProcessor implements Consumer<GroupMessage> {
|
||||||
|
|
||||||
|
private static final Logger logger = EnvoyLog.getLogger(ReceivedGroupMessageProcessor.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void accept(GroupMessage groupMessage) {
|
||||||
|
if (groupMessage.getStatus() == MessageStatus.WAITING || groupMessage.getStatus() == MessageStatus.READ)
|
||||||
|
logger.warning("The groupMessage has the unexpected status " + groupMessage.getStatus());
|
||||||
|
|
||||||
|
// Dispatch event
|
||||||
|
EventBus.getInstance().dispatch(new MessageCreationEvent(groupMessage));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
36
src/main/java/envoy/client/net/ReceivedMessageProcessor.java
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
package envoy.client.net;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import envoy.client.event.MessageCreationEvent;
|
||||||
|
import envoy.data.Message;
|
||||||
|
import envoy.data.Message.MessageStatus;
|
||||||
|
import envoy.event.EventBus;
|
||||||
|
import envoy.util.EnvoyLog;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Project: <strong>envoy-client</strong><br>
|
||||||
|
* File: <strong>ReceivedMessageProcessor.java</strong><br>
|
||||||
|
* Created: <strong>31.12.2019</strong><br>
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @since Envoy Client v0.3-alpha
|
||||||
|
*/
|
||||||
|
public class ReceivedMessageProcessor implements Consumer<Message> {
|
||||||
|
|
||||||
|
private static final Logger logger = EnvoyLog.getLogger(ReceivedMessageProcessor.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void accept(Message message) {
|
||||||
|
if (message.getStatus() != MessageStatus.SENT) logger.log(Level.WARNING, "The message has the unexpected status " + message.getStatus());
|
||||||
|
else {
|
||||||
|
// Update status to RECEIVED
|
||||||
|
message.nextStatus();
|
||||||
|
|
||||||
|
// Dispatch event
|
||||||
|
EventBus.getInstance().dispatch(new MessageCreationEvent(message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
118
src/main/java/envoy/client/net/Receiver.java
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
package envoy.client.net;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.ObjectInputStream;
|
||||||
|
import java.net.SocketException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import envoy.util.EnvoyLog;
|
||||||
|
import envoy.util.SerializationUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receives objects from the server and passes them to processor objects based
|
||||||
|
* on their class.
|
||||||
|
* <p>
|
||||||
|
* Project: <strong>envoy-client</strong><br>
|
||||||
|
* File: <strong>Receiver.java</strong><br>
|
||||||
|
* Created: <strong>30.12.2019</strong><br>
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @since Envoy Client v0.3-alpha
|
||||||
|
*/
|
||||||
|
public class Receiver extends Thread {
|
||||||
|
|
||||||
|
private final InputStream in;
|
||||||
|
private final Map<Class<?>, Consumer<?>> processors = new HashMap<>();
|
||||||
|
|
||||||
|
private static final Logger logger = EnvoyLog.getLogger(Receiver.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance of {@link Receiver}.
|
||||||
|
*
|
||||||
|
* @param in the {@link InputStream} to parse objects from
|
||||||
|
* @since Envoy Client v0.3-alpha
|
||||||
|
*/
|
||||||
|
public Receiver(InputStream in) {
|
||||||
|
super("Receiver");
|
||||||
|
this.in = in;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the receiver loop. When an object is read, it is passed to the
|
||||||
|
* appropriate processor.
|
||||||
|
*
|
||||||
|
* @since Envoy Client v0.3-alpha
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
// Read object length
|
||||||
|
final byte[] lenBytes = new byte[4];
|
||||||
|
in.read(lenBytes);
|
||||||
|
final int len = SerializationUtils.bytesToInt(lenBytes, 0);
|
||||||
|
logger.log(Level.FINEST, "Expecting object of length " + len + ".");
|
||||||
|
|
||||||
|
// Read object into byte array
|
||||||
|
final byte[] objBytes = new byte[len];
|
||||||
|
final int bytesRead = in.read(objBytes);
|
||||||
|
logger.log(Level.FINEST, "Read " + bytesRead + " bytes.");
|
||||||
|
|
||||||
|
// Catch LV encoding errors
|
||||||
|
if (len != bytesRead) {
|
||||||
|
logger.log(Level.WARNING,
|
||||||
|
String.format("LV encoding violated: expected %d bytes, received %d bytes. Discarding object...", len, bytesRead));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try (ObjectInputStream oin = new ObjectInputStream(new ByteArrayInputStream(objBytes))) {
|
||||||
|
final Object obj = oin.readObject();
|
||||||
|
logger.log(Level.FINE, "Received " + obj);
|
||||||
|
|
||||||
|
// Get appropriate processor
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
final Consumer processor = processors.get(obj.getClass());
|
||||||
|
if (processor == null)
|
||||||
|
logger.log(Level.WARNING, String.format("The received object has the %s for which no processor is defined.", obj.getClass()));
|
||||||
|
else processor.accept(obj);
|
||||||
|
}
|
||||||
|
} catch (final SocketException e) {
|
||||||
|
// Connection probably closed by client.
|
||||||
|
return;
|
||||||
|
} catch (final Exception e) {
|
||||||
|
logger.log(Level.SEVERE, "Error on receiver thread", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an object processor to this {@link Receiver}. It will be called once an
|
||||||
|
* object of the accepted class has been received.
|
||||||
|
*
|
||||||
|
* @param processorClass the object class accepted by the processor
|
||||||
|
* @param processor the object processor
|
||||||
|
* @since Envoy Client v0.3-alpha
|
||||||
|
*/
|
||||||
|
public <T> void registerProcessor(Class<T> processorClass, Consumer<T> processor) { processors.put(processorClass, processor); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a map of object processors to this {@link Receiver}.
|
||||||
|
*
|
||||||
|
* @param processors the processors to add the processors to add
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public void registerProcessors(Map<Class<?>, ? extends Consumer<?>> processors) { this.processors.putAll(processors); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all object processors registered at this {@link Receiver}.
|
||||||
|
*
|
||||||
|
* @since Envoy Client v0.3-alpha
|
||||||
|
*/
|
||||||
|
public void removeAllProcessors() { processors.clear(); }
|
||||||
|
}
|
100
src/main/java/envoy/client/net/WriteProxy.java
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
package envoy.client.net;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import envoy.client.data.Cache;
|
||||||
|
import envoy.client.data.LocalDB;
|
||||||
|
import envoy.data.Message;
|
||||||
|
import envoy.event.MessageStatusChange;
|
||||||
|
import envoy.util.EnvoyLog;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements methods to send {@link Message}s and
|
||||||
|
* {@link MessageStatusChange}s to the server or cache them inside a
|
||||||
|
* {@link LocalDB} depending on the online status.
|
||||||
|
* <p>
|
||||||
|
* Project: <strong>envoy-client</strong><br>
|
||||||
|
* File: <strong>WriteProxy.java</strong><br>
|
||||||
|
* Created: <strong>6 Feb 2020</strong><br>
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @since Envoy Client v0.3-alpha
|
||||||
|
*/
|
||||||
|
public class WriteProxy {
|
||||||
|
|
||||||
|
private final Client client;
|
||||||
|
private final LocalDB localDB;
|
||||||
|
|
||||||
|
private static final Logger logger = EnvoyLog.getLogger(WriteProxy.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes a write proxy using a client and a local database. The
|
||||||
|
* corresponding cache processors are injected into the caches.
|
||||||
|
*
|
||||||
|
* @param client the client used to send messages and message status change
|
||||||
|
* events
|
||||||
|
* @param localDB the local database used to cache messages and message status
|
||||||
|
* change events
|
||||||
|
* @since Envoy Client v0.3-alpha
|
||||||
|
*/
|
||||||
|
public WriteProxy(Client client, LocalDB localDB) {
|
||||||
|
this.client = client;
|
||||||
|
this.localDB = localDB;
|
||||||
|
|
||||||
|
// Initialize cache processors for messages and message status change events
|
||||||
|
localDB.getCacheMap().get(Message.class).setProcessor(msg -> {
|
||||||
|
try {
|
||||||
|
logger.log(Level.FINER, "Sending cached " + msg);
|
||||||
|
client.sendMessage(msg);
|
||||||
|
} catch (final IOException e) {
|
||||||
|
logger.log(Level.SEVERE, "Could not send cached message: ", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
localDB.getCacheMap().get(MessageStatusChange.class).setProcessor(evt -> {
|
||||||
|
logger.log(Level.FINER, "Sending cached " + evt);
|
||||||
|
try {
|
||||||
|
client.sendEvent(evt);
|
||||||
|
} catch (final IOException e) {
|
||||||
|
logger.log(Level.SEVERE, "Could not send cached message status change event: ", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends cached {@link Message}s and {@link MessageStatusChange}s to the
|
||||||
|
* server.
|
||||||
|
*
|
||||||
|
* @since Envoy Client v0.3-alpha
|
||||||
|
*/
|
||||||
|
public void flushCache() {
|
||||||
|
localDB.getCacheMap().getMap().values().forEach(Cache::relay);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delivers a message to the server if online. Otherwise the message is cached
|
||||||
|
* inside the local database.
|
||||||
|
*
|
||||||
|
* @param message the message to send
|
||||||
|
* @throws IOException if the message could not be sent
|
||||||
|
* @since Envoy Client v0.3-alpha
|
||||||
|
*/
|
||||||
|
public void writeMessage(Message message) throws IOException {
|
||||||
|
if (client.isOnline()) client.sendMessage(message);
|
||||||
|
else localDB.getCacheMap().getApplicable(Message.class).accept(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delivers a message status change event to the server if online. Otherwise the
|
||||||
|
* event is cached inside the local database.
|
||||||
|
*
|
||||||
|
* @param evt the event to send
|
||||||
|
* @throws IOException if the event could not be sent
|
||||||
|
* @since Envoy Client v0.3-alpha
|
||||||
|
*/
|
||||||
|
public void writeMessageStatusChange(MessageStatusChange evt) throws IOException {
|
||||||
|
if (client.isOnline()) client.sendEvent(evt);
|
||||||
|
else localDB.getCacheMap().getApplicable(MessageStatusChange.class).accept(evt);
|
||||||
|
}
|
||||||
|
}
|
9
src/main/java/envoy/client/net/package-info.java
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
/**
|
||||||
|
* This package contains all classes related to client-server communication.
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @author Leon Hofmeister
|
||||||
|
* @author Maximilian Käfer
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
package envoy.client.net;
|
49
src/main/java/envoy/client/ui/AudioControl.java
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
package envoy.client.ui;
|
||||||
|
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javafx.scene.control.Alert;
|
||||||
|
import javafx.scene.control.Alert.AlertType;
|
||||||
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.layout.HBox;
|
||||||
|
|
||||||
|
import envoy.client.data.audio.AudioPlayer;
|
||||||
|
import envoy.exception.EnvoyException;
|
||||||
|
import envoy.util.EnvoyLog;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables the play back of audio clips through a button.
|
||||||
|
* <p>
|
||||||
|
* Project: <strong>envoy-client</strong><br>
|
||||||
|
* File: <strong>AudioControl.java</strong><br>
|
||||||
|
* Created: <strong>05.07.2020</strong><br>
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public final class AudioControl extends HBox {
|
||||||
|
|
||||||
|
private AudioPlayer player = new AudioPlayer();
|
||||||
|
|
||||||
|
private static final Logger logger = EnvoyLog.getLogger(AudioControl.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the audio control.
|
||||||
|
*
|
||||||
|
* @param audioData the audio data to play.
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public AudioControl(byte[] audioData) {
|
||||||
|
var button = new Button("Play");
|
||||||
|
button.setOnAction(e -> {
|
||||||
|
try {
|
||||||
|
player.play(audioData);
|
||||||
|
} catch (EnvoyException ex) {
|
||||||
|
logger.log(Level.SEVERE, "Could not play back audio: ", ex);
|
||||||
|
new Alert(AlertType.ERROR, "Could not play back audio").showAndWait();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
getChildren().add(button);
|
||||||
|
}
|
||||||
|
}
|
169
src/main/java/envoy/client/ui/ClearableTextField.java
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
package envoy.client.ui;
|
||||||
|
|
||||||
|
import javafx.beans.property.BooleanProperty;
|
||||||
|
import javafx.beans.property.ObjectProperty;
|
||||||
|
import javafx.beans.property.StringProperty;
|
||||||
|
import javafx.event.ActionEvent;
|
||||||
|
import javafx.event.EventHandler;
|
||||||
|
import javafx.scene.control.*;
|
||||||
|
import javafx.scene.image.ImageView;
|
||||||
|
import javafx.scene.layout.Background;
|
||||||
|
import javafx.scene.layout.ColumnConstraints;
|
||||||
|
import javafx.scene.layout.GridPane;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class offers a text field that is automatically equipped with a clear
|
||||||
|
* button.
|
||||||
|
* <p>
|
||||||
|
* Project: <strong>envoy-client</strong><br>
|
||||||
|
* File: <strong>ClearableTextField.java</strong><br>
|
||||||
|
* Created: <strong>25.06.2020</strong><br>
|
||||||
|
*
|
||||||
|
* @author Leon Hofmeister
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public class ClearableTextField extends GridPane {
|
||||||
|
|
||||||
|
private final TextField textField;
|
||||||
|
|
||||||
|
private final Button clearButton;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new {@code ClearableTextField} with no initial text and icon
|
||||||
|
* size 16.
|
||||||
|
*
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public ClearableTextField() { this("", 16); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new {@code ClearableTextField} with initial text and a
|
||||||
|
* predetermined icon size.
|
||||||
|
*
|
||||||
|
* @param text the text that should be displayed by default
|
||||||
|
* @param size the size of the icon
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public ClearableTextField(String text, int size) {
|
||||||
|
// initializing the textField and the button
|
||||||
|
textField = new TextField(text);
|
||||||
|
clearButton = new Button("", new ImageView(IconUtil.loadIconThemeSensitive("clear_button", size)));
|
||||||
|
clearButton.setOnAction(e -> textField.clear());
|
||||||
|
clearButton.setFocusTraversable(false);
|
||||||
|
clearButton.getStyleClass().clear();
|
||||||
|
clearButton.setBackground(Background.EMPTY);
|
||||||
|
// Adding the two elements to the GridPane
|
||||||
|
add(textField, 0, 0, 2, 1);
|
||||||
|
add(clearButton, 1, 0, 1, 1);
|
||||||
|
// Setting the percent - widths of the two columns.
|
||||||
|
// Used to locate the button on the right.
|
||||||
|
final var columnConstraints = new ColumnConstraints();
|
||||||
|
columnConstraints.setPercentWidth(90);
|
||||||
|
getColumnConstraints().add(columnConstraints);
|
||||||
|
final var columnConstraints2 = new ColumnConstraints();
|
||||||
|
columnConstraints2.setPercentWidth(10);
|
||||||
|
getColumnConstraints().add(columnConstraints2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the underlying {@code textField}
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public TextField getTextField() { return textField; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method offers the freedom to perform custom actions when the
|
||||||
|
* {@code clearButton} has been pressed.
|
||||||
|
* <p>
|
||||||
|
* The default is
|
||||||
|
* <b><code> e -> {clearableTextField.getTextField().clear();}</code></b>
|
||||||
|
*
|
||||||
|
* @param onClearButtonAction the action that should be performed
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public void setClearButtonListener(EventHandler<ActionEvent> onClearButtonAction) { clearButton.setOnAction(onClearButtonAction); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the current property of the prompt text
|
||||||
|
* @see javafx.scene.control.TextInputControl#promptTextProperty()
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public final StringProperty promptTextProperty() { return textField.promptTextProperty(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the current prompt text
|
||||||
|
* @see javafx.scene.control.TextInputControl#getPromptText()
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public final String getPromptText() { return textField.getPromptText(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param value the prompt text to display
|
||||||
|
* @see javafx.scene.control.TextInputControl#setPromptText(java.lang.String)
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public final void setPromptText(String value) { textField.setPromptText(value); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the current property of the tooltip
|
||||||
|
* @see javafx.scene.control.Control#tooltipProperty()
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public final ObjectProperty<Tooltip> tooltipProperty() { return textField.tooltipProperty(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param value the new tooltip
|
||||||
|
* @see javafx.scene.control.Control#setTooltip(javafx.scene.control.Tooltip)
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public final void setTooltip(Tooltip value) { textField.setTooltip(value); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the current tooltip
|
||||||
|
* @see javafx.scene.control.Control#getTooltip()
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public final Tooltip getTooltip() { return textField.getTooltip(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the current property of the context menu
|
||||||
|
* @see javafx.scene.control.Control#contextMenuProperty()
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public final ObjectProperty<ContextMenu> contextMenuProperty() { return textField.contextMenuProperty(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param value the new context menu
|
||||||
|
* @see javafx.scene.control.Control#setContextMenu(javafx.scene.control.ContextMenu)
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public final void setContextMenu(ContextMenu value) { textField.setContextMenu(value); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the current context menu
|
||||||
|
* @see javafx.scene.control.Control#getContextMenu()
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public final ContextMenu getContextMenu() { return textField.getContextMenu(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param value whether this ClearableTextField should be editable
|
||||||
|
* @see javafx.scene.control.TextInputControl#setEditable(boolean)
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public final void setEditable(boolean value) { textField.setEditable(value); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the current property whether this ClearableTextField is editable
|
||||||
|
* @see javafx.scene.control.TextInputControl#editableProperty()
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public final BooleanProperty editableProperty() { return textField.editableProperty(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return whether this {@code ClearableTextField} is editable
|
||||||
|
* @see javafx.scene.control.TextInputControl#isEditable()
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public final boolean isEditable() { return textField.isEditable(); }
|
||||||
|
}
|
160
src/main/java/envoy/client/ui/IconUtil.java
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
package envoy.client.ui;
|
||||||
|
|
||||||
|
import java.util.EnumMap;
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
import javafx.scene.image.Image;
|
||||||
|
|
||||||
|
import envoy.client.data.Settings;
|
||||||
|
import envoy.util.EnvoyLog;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides static utility methods for loading icons from the resource
|
||||||
|
* folder.
|
||||||
|
* <p>
|
||||||
|
* Project: <strong>envoy-client</strong><br>
|
||||||
|
* File: <strong>IconUtil.java</strong><br>
|
||||||
|
* Created: <strong>16.03.2020</strong><br>
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public class IconUtil {
|
||||||
|
|
||||||
|
private IconUtil() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads an image from the resource folder.
|
||||||
|
*
|
||||||
|
* @param path the path to the icon inside the resource folder
|
||||||
|
* @return the loaded image
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public static Image load(String path) {
|
||||||
|
Image image = null;
|
||||||
|
try {
|
||||||
|
image = new Image(IconUtil.class.getResource(path).toExternalForm());
|
||||||
|
} catch (final NullPointerException e) {
|
||||||
|
EnvoyLog.getLogger(IconUtil.class).log(Level.WARNING, String.format("Could not load image at path %s: ", path), e);
|
||||||
|
}
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads an image from the resource folder and scales it to the given size.
|
||||||
|
*
|
||||||
|
* @param path the path to the icon inside the resource folder
|
||||||
|
* @param size the size to scale the icon to
|
||||||
|
* @return the scaled image
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public static Image load(String path, int size) {
|
||||||
|
Image image = null;
|
||||||
|
try {
|
||||||
|
image = new Image(IconUtil.class.getResource(path).toExternalForm(), size, size, true, true);
|
||||||
|
} catch (final NullPointerException e) {
|
||||||
|
EnvoyLog.getLogger(IconUtil.class).log(Level.WARNING, String.format("Could not load image at path %s: ", path), e);
|
||||||
|
}
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads a {@code .png} image from the sub-folder {@code /icons/} of the
|
||||||
|
* resource folder.<br>
|
||||||
|
* The suffix {@code .png} is automatically appended.
|
||||||
|
*
|
||||||
|
* @param name the image name without the .png suffix
|
||||||
|
* @return the loaded image
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
* @apiNote let's load a sample image {@code /icons/abc.png}.<br>
|
||||||
|
* To do that, we only have to call {@code IconUtil.loadIcon("abc")}
|
||||||
|
*/
|
||||||
|
public static Image loadIcon(String name) { return load("/icons/" + name + ".png"); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads a {@code .png} image from the sub-folder {@code /icons/} of the
|
||||||
|
* resource folder and scales it to the given size.<br>
|
||||||
|
* The suffix {@code .png} is automatically appended.
|
||||||
|
*
|
||||||
|
* @param name the image name without the .png suffix
|
||||||
|
* @param size the size of the image to scale to
|
||||||
|
* @return the loaded image
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
* @apiNote let's load a sample image {@code /icons/abc.png} in size 16.<br>
|
||||||
|
* To do that, we only have to call
|
||||||
|
* {@code IconUtil.loadIcon("abc", 16)}
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
* from the sub-folder {@code /icons/dark/} or {@code /icons/light/} of the
|
||||||
|
* resource folder.
|
||||||
|
* <p>
|
||||||
|
* The suffix {@code .png} is automatically appended.
|
||||||
|
*
|
||||||
|
* @param name the image name without the "black" or "white" suffix and without
|
||||||
|
* the .png suffix
|
||||||
|
* @return the loaded image
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
* @apiNote let's take two sample images {@code /icons/dark/abc.png} and
|
||||||
|
* {@code /icons/light/abc.png}, and load one of them.<br>
|
||||||
|
* To do that theme sensitive, we only have to call
|
||||||
|
* {@code IconUtil.loadIconThemeSensitive("abc")}
|
||||||
|
*/
|
||||||
|
public static Image loadIconThemeSensitive(String name) { return loadIcon(themeSpecificSubFolder() + name); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads a {@code .png} image whose design depends on the currently active theme
|
||||||
|
* from the sub-folder {@code /icons/dark/} or {@code /icons/light/} of the
|
||||||
|
* resource folder and scales it to the given size.
|
||||||
|
* <p>
|
||||||
|
* The suffix {@code .png} is automatically appended.
|
||||||
|
*
|
||||||
|
* @param name the image name without the .png suffix
|
||||||
|
* @param size the size of the image to scale to
|
||||||
|
* @return the loaded image
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
* @apiNote let's take two sample images {@code /icons/dark/abc.png} and
|
||||||
|
* {@code /icons/light/abc.png}, and load one of them in size 16.<br>
|
||||||
|
* To do that theme sensitive, we only have to call
|
||||||
|
* {@code IconUtil.loadIconThemeSensitive("abc", 16)}
|
||||||
|
*/
|
||||||
|
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 constants with {@code .png} extension and be located inside a
|
||||||
|
* 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 enumClass the class of the enum
|
||||||
|
* @param size the size to scale the images to
|
||||||
|
* @return a map containing the loaded images with the corresponding enum
|
||||||
|
* constants as keys
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public static <T extends Enum<T>> EnumMap<T, Image> loadByEnum(Class<T> enumClass, int size) {
|
||||||
|
final var icons = new EnumMap<T, Image>(enumClass);
|
||||||
|
final var path = "/icons/" + enumClass.getSimpleName().toLowerCase() + "/";
|
||||||
|
for (final var e : EnumSet.allOf(enumClass))
|
||||||
|
icons.put(e, load(path + e.toString().toLowerCase() + ".png", size));
|
||||||
|
return icons;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method should be called if the display of an image depends upon the
|
||||||
|
* currently active theme.<br>
|
||||||
|
* In case of a default theme, the string returned will be
|
||||||
|
* ({@code dark/} or {@code light/}), otherwise it will be empty.
|
||||||
|
*
|
||||||
|
* @return the theme specific folder
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public static String themeSpecificSubFolder() {
|
||||||
|
return Settings.getInstance().isUsingDefaultTheme() ? Settings.getInstance().getCurrentTheme() + "/" : "";
|
||||||
|
}
|
||||||
|
}
|
25
src/main/java/envoy/client/ui/Restorable.java
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
package envoy.client.ui;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This interface defines an action that should be performed when a scene gets
|
||||||
|
* restored from the scene stack in {@link SceneContext}.
|
||||||
|
* <p>
|
||||||
|
* Project: <strong>envoy-client</strong><br>
|
||||||
|
* File: <strong>Restorable.java</strong><br>
|
||||||
|
* Created: <strong>03.07.2020</strong><br>
|
||||||
|
*
|
||||||
|
* @author Leon Hofmeister
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface Restorable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is getting called when a scene gets restored.<br>
|
||||||
|
* Hence, it can contain anything that should be done when the underlying scene
|
||||||
|
* gets restored.
|
||||||
|
*
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
void onRestore();
|
||||||
|
}
|
179
src/main/java/envoy/client/ui/SceneContext.java
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
package envoy.client.ui;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Stack;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
import javafx.fxml.FXMLLoader;
|
||||||
|
import javafx.scene.Parent;
|
||||||
|
import javafx.scene.Scene;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
|
||||||
|
import envoy.client.data.Settings;
|
||||||
|
import envoy.client.event.ThemeChangeEvent;
|
||||||
|
import envoy.event.EventBus;
|
||||||
|
import envoy.util.EnvoyLog;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages a stack of scenes. The most recently added scene is displayed inside
|
||||||
|
* a stage. When a scene is removed from the stack, its predecessor is
|
||||||
|
* displayed.
|
||||||
|
* <p>
|
||||||
|
* When a scene is loaded, the style sheet for the current theme is applied to
|
||||||
|
* it.
|
||||||
|
* <p>
|
||||||
|
* Project: <strong>envoy-client</strong><br>
|
||||||
|
* File: <strong>SceneContext.java</strong><br>
|
||||||
|
* Created: <strong>06.06.2020</strong><br>
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public final class SceneContext {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains information about different scenes and their FXML resource files.
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public 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 contact search screen is displayed.
|
||||||
|
*
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
CONTACT_SEARCH_SCENE("/fxml/ContactSearchScene.fxml"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The scene in which the group creation screen is displayed.
|
||||||
|
*
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
GROUP_CREATION_SCENE("/fxml/GroupCreationScene.fxml"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The scene in which the login screen is displayed.
|
||||||
|
*
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
LOGIN_SCENE("/fxml/LoginScene.fxml"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The scene in which the info screen is displayed.
|
||||||
|
*
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
MESSAGE_INFO_SCENE("/fxml/MessageInfoScene.fxml");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The path to the FXML resource.
|
||||||
|
*/
|
||||||
|
public final String path;
|
||||||
|
|
||||||
|
SceneInfo(String path) { this.path = path; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Stage stage;
|
||||||
|
private final FXMLLoader loader = new FXMLLoader();
|
||||||
|
private final Stack<Scene> sceneStack = new Stack<>();
|
||||||
|
private final Stack<Object> controllerStack = new Stack<>();
|
||||||
|
|
||||||
|
private static final Settings settings = Settings.getInstance();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the scene context.
|
||||||
|
*
|
||||||
|
* @param stage the stage in which scenes will be displayed
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public SceneContext(Stage stage) {
|
||||||
|
this.stage = stage;
|
||||||
|
EventBus.getInstance().register(ThemeChangeEvent.class, theme -> applyCSS());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads a new scene specified by a scene info.
|
||||||
|
*
|
||||||
|
* @param sceneInfo specifies the scene to load
|
||||||
|
* @throws RuntimeException if the loading process fails
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public void load(SceneInfo sceneInfo) {
|
||||||
|
loader.setRoot(null);
|
||||||
|
loader.setController(null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
final var rootNode = (Parent) loader.load(getClass().getResourceAsStream(sceneInfo.path));
|
||||||
|
final var scene = new Scene(rootNode);
|
||||||
|
controllerStack.push(loader.getController());
|
||||||
|
|
||||||
|
sceneStack.push(scene);
|
||||||
|
stage.setScene(scene);
|
||||||
|
applyCSS();
|
||||||
|
stage.sizeToScene();
|
||||||
|
stage.show();
|
||||||
|
} catch (final IOException e) {
|
||||||
|
EnvoyLog.getLogger(SceneContext.class).log(Level.SEVERE, String.format("Could not load scene for %s: ", sceneInfo), e);
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the current scene and displays the previous one.
|
||||||
|
*
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public void pop() {
|
||||||
|
sceneStack.pop();
|
||||||
|
controllerStack.pop();
|
||||||
|
if (!sceneStack.isEmpty()) {
|
||||||
|
final var newScene = sceneStack.peek();
|
||||||
|
stage.setScene(newScene);
|
||||||
|
applyCSS();
|
||||||
|
stage.sizeToScene();
|
||||||
|
// If the controller implements the Restorable interface,
|
||||||
|
// the actions to perform on restoration will be executed here
|
||||||
|
final var controller = controllerStack.peek();
|
||||||
|
if (controller instanceof Restorable) ((Restorable) controller).onRestore();
|
||||||
|
}
|
||||||
|
stage.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyCSS() {
|
||||||
|
if (!sceneStack.isEmpty()) {
|
||||||
|
final var styleSheets = stage.getScene().getStylesheets();
|
||||||
|
final var themeCSS = "/css/" + settings.getCurrentTheme() + ".css";
|
||||||
|
styleSheets.clear();
|
||||||
|
styleSheets.addAll(getClass().getResource("/css/base.css").toExternalForm(), getClass().getResource(themeCSS).toExternalForm());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param <T> the type of the controller
|
||||||
|
* @return the controller used by the current scene
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public <T> T getController() { return (T) controllerStack.peek(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the stage in which the scenes are displayed
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public Stage getStage() { return stage; }
|
||||||
|
}
|
135
src/main/java/envoy/client/ui/Startup.java
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
package envoy.client.ui;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javafx.application.Application;
|
||||||
|
import javafx.scene.control.Alert;
|
||||||
|
import javafx.scene.control.Alert.AlertType;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
|
||||||
|
import envoy.client.data.*;
|
||||||
|
import envoy.client.net.Client;
|
||||||
|
import envoy.client.ui.SceneContext.SceneInfo;
|
||||||
|
import envoy.client.ui.controller.LoginScene;
|
||||||
|
import envoy.data.GroupMessage;
|
||||||
|
import envoy.data.Message;
|
||||||
|
import envoy.event.GroupMessageStatusChange;
|
||||||
|
import envoy.event.MessageStatusChange;
|
||||||
|
import envoy.exception.EnvoyException;
|
||||||
|
import envoy.util.EnvoyLog;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles application startup and shutdown.
|
||||||
|
* <p>
|
||||||
|
* Project: <strong>envoy-client</strong><br>
|
||||||
|
* File: <strong>Startup.java</strong><br>
|
||||||
|
* Created: <strong>26.03.2020</strong><br>
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @author Maximilian Käfer
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public final class Startup extends Application {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The version of this client. Used to verify compatibility with the server.
|
||||||
|
*
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public static final String VERSION = "0.1-beta";
|
||||||
|
|
||||||
|
private LocalDB localDB;
|
||||||
|
private Client client;
|
||||||
|
|
||||||
|
private static final ClientConfig config = ClientConfig.getInstance();
|
||||||
|
private static final Logger logger = EnvoyLog.getLogger(Startup.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the configuration, initializes the client and the local database and
|
||||||
|
* delegates the rest of the startup process to {@link LoginScene}.
|
||||||
|
*
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void start(Stage stage) throws Exception {
|
||||||
|
try {
|
||||||
|
// Load the configuration from client.properties first
|
||||||
|
final Properties properties = new Properties();
|
||||||
|
properties.load(Startup.class.getClassLoader().getResourceAsStream("client.properties"));
|
||||||
|
config.load(properties);
|
||||||
|
|
||||||
|
// Override configuration values with command line arguments
|
||||||
|
final String[] args = getParameters().getRaw().toArray(new String[0]);
|
||||||
|
if (args.length > 0) config.load(args);
|
||||||
|
|
||||||
|
// Check if all mandatory configuration values have been initialized
|
||||||
|
if (!config.isInitialized()) throw new EnvoyException("Configuration is not fully initialized");
|
||||||
|
} catch (final Exception e) {
|
||||||
|
new Alert(AlertType.ERROR, "Error loading configuration values:\n" + e);
|
||||||
|
logger.log(Level.SEVERE, "Error loading configuration values: ", e);
|
||||||
|
e.printStackTrace();
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup logger for the envoy package
|
||||||
|
EnvoyLog.initialize(config);
|
||||||
|
EnvoyLog.attach("envoy");
|
||||||
|
EnvoyLog.setFileLevelBarrier(config.getFileLevelBarrier());
|
||||||
|
EnvoyLog.setConsoleLevelBarrier(config.getConsoleLevelBarrier());
|
||||||
|
|
||||||
|
logger.log(Level.INFO, "Envoy starting...");
|
||||||
|
|
||||||
|
// Initialize the local database
|
||||||
|
if (config.isIgnoreLocalDB()) {
|
||||||
|
localDB = new TransientLocalDB();
|
||||||
|
new Alert(AlertType.WARNING, "Ignoring local database.\nMessages will not be saved!").showAndWait();
|
||||||
|
} else try {
|
||||||
|
localDB = new PersistentLocalDB(new File(config.getHomeDirectory(), config.getLocalDB().getPath()));
|
||||||
|
} catch (final IOException e3) {
|
||||||
|
logger.log(Level.SEVERE, "Could not initialize local database: ", e3);
|
||||||
|
new Alert(AlertType.ERROR, "Could not initialize local database!\n" + e3).showAndWait();
|
||||||
|
System.exit(1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize client and unread message cache
|
||||||
|
client = new Client();
|
||||||
|
|
||||||
|
final var cacheMap = new CacheMap();
|
||||||
|
cacheMap.put(Message.class, new Cache<Message>());
|
||||||
|
cacheMap.put(GroupMessage.class, new Cache<GroupMessage>());
|
||||||
|
cacheMap.put(MessageStatusChange.class, new Cache<MessageStatusChange>());
|
||||||
|
cacheMap.put(GroupMessageStatusChange.class, new Cache<GroupMessageStatusChange>());
|
||||||
|
|
||||||
|
stage.setTitle("Envoy");
|
||||||
|
stage.getIcons().add(IconUtil.loadIcon("envoy_logo"));
|
||||||
|
|
||||||
|
final var sceneContext = new SceneContext(stage);
|
||||||
|
sceneContext.load(SceneInfo.LOGIN_SCENE);
|
||||||
|
sceneContext.<LoginScene>getController().initializeData(client, localDB, cacheMap, sceneContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the client connection and saves the local database and settings.
|
||||||
|
*
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void stop() {
|
||||||
|
try {
|
||||||
|
logger.log(Level.INFO, "Closing connection...");
|
||||||
|
client.close();
|
||||||
|
|
||||||
|
logger.log(Level.INFO, "Saving local database and settings...");
|
||||||
|
localDB.save();
|
||||||
|
Settings.getInstance().save();
|
||||||
|
logger.log(Level.INFO, "Envoy was terminated by its user");
|
||||||
|
} catch (final Exception e) {
|
||||||
|
logger.log(Level.SEVERE, "Unable to save local files: ", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
100
src/main/java/envoy/client/ui/StatusTrayIcon.java
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
package envoy.client.ui;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.TrayIcon.MessageType;
|
||||||
|
import java.awt.event.WindowAdapter;
|
||||||
|
import java.awt.event.WindowEvent;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
import envoy.client.event.MessageCreationEvent;
|
||||||
|
import envoy.data.Message;
|
||||||
|
import envoy.event.EventBus;
|
||||||
|
import envoy.exception.EnvoyException;
|
||||||
|
import envoy.util.EnvoyLog;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Project: <strong>envoy-client</strong><br>
|
||||||
|
* File: <strong>StatusTrayIcon.java</strong><br>
|
||||||
|
* Created: <strong>3 Dec 2019</strong><br>
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @since Envoy Client v0.2-alpha
|
||||||
|
*/
|
||||||
|
public class StatusTrayIcon {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link TrayIcon} provided by the System Tray API for controlling the
|
||||||
|
* system tray. This includes displaying the icon, but also creating
|
||||||
|
* notifications when new messages are received.
|
||||||
|
*/
|
||||||
|
private final TrayIcon trayIcon;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A received {@link Message} is only displayed as a system tray notification if
|
||||||
|
* this variable is set to {@code true}.
|
||||||
|
*/
|
||||||
|
private boolean displayMessages = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a {@link StatusTrayIcon} with the Envoy logo, a tool tip and a pop-up
|
||||||
|
* menu.
|
||||||
|
*
|
||||||
|
* @param focusTarget the {@link Window} which focus determines if message
|
||||||
|
* notifications are displayed
|
||||||
|
* @throws EnvoyException if the currently used OS does not support the System
|
||||||
|
* Tray API
|
||||||
|
* @since Envoy Client v0.2-alpha
|
||||||
|
*/
|
||||||
|
public StatusTrayIcon(Window focusTarget) throws EnvoyException {
|
||||||
|
if (!SystemTray.isSupported()) throw new EnvoyException("The Envoy tray icon is not supported.");
|
||||||
|
|
||||||
|
final ClassLoader loader = Thread.currentThread().getContextClassLoader();
|
||||||
|
final Image img = Toolkit.getDefaultToolkit().createImage(loader.getResource("envoy_logo.png"));
|
||||||
|
trayIcon = new TrayIcon(img, "Envoy Client");
|
||||||
|
trayIcon.setImageAutoSize(true);
|
||||||
|
trayIcon.setToolTip("You are notified if you have unread messages.");
|
||||||
|
|
||||||
|
final PopupMenu popup = new PopupMenu();
|
||||||
|
|
||||||
|
final MenuItem exitMenuItem = new MenuItem("Exit");
|
||||||
|
exitMenuItem.addActionListener(evt -> System.exit(0));
|
||||||
|
popup.add(exitMenuItem);
|
||||||
|
|
||||||
|
trayIcon.setPopupMenu(popup);
|
||||||
|
|
||||||
|
// Only display messages if the chat window is not focused
|
||||||
|
focusTarget.addWindowFocusListener(new WindowAdapter() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void windowGainedFocus(WindowEvent e) { displayMessages = false; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void windowLostFocus(WindowEvent e) { displayMessages = true; }
|
||||||
|
});
|
||||||
|
|
||||||
|
// Show the window if the user clicks on the icon
|
||||||
|
trayIcon.addActionListener(evt -> { focusTarget.setVisible(true); focusTarget.requestFocus(); });
|
||||||
|
|
||||||
|
// Start processing message events
|
||||||
|
// TODO: Handle other message types
|
||||||
|
EventBus.getInstance()
|
||||||
|
.register(MessageCreationEvent.class,
|
||||||
|
evt -> { if (displayMessages) trayIcon.displayMessage("New message received", evt.get().getText(), MessageType.INFO); });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes this {@link StatusTrayIcon} appear in the system tray.
|
||||||
|
*
|
||||||
|
* @throws EnvoyException if the status icon could not be attaches to the system
|
||||||
|
* tray for system-internal reasons
|
||||||
|
* @since Envoy Client v0.2-alpha
|
||||||
|
*/
|
||||||
|
public void show() throws EnvoyException {
|
||||||
|
try {
|
||||||
|
SystemTray.getSystemTray().add(trayIcon);
|
||||||
|
} catch (final AWTException e) {
|
||||||
|
EnvoyLog.getLogger(StatusTrayIcon.class).log(Level.INFO, "Could not display StatusTrayIcon: ", e);
|
||||||
|
throw new EnvoyException("Could not attach Envoy tray icon to system tray.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
570
src/main/java/envoy/client/ui/controller/ChatScene.java
Normal file
@ -0,0 +1,570 @@
|
|||||||
|
package envoy.client.ui.controller;
|
||||||
|
|
||||||
|
import java.awt.Toolkit;
|
||||||
|
import java.awt.datatransfer.StringSelection;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javafx.animation.RotateTransition;
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.control.*;
|
||||||
|
import javafx.scene.control.Alert.AlertType;
|
||||||
|
import javafx.scene.image.Image;
|
||||||
|
import javafx.scene.image.ImageView;
|
||||||
|
import javafx.scene.input.KeyCode;
|
||||||
|
import javafx.scene.input.KeyEvent;
|
||||||
|
import javafx.scene.layout.GridPane;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
import javafx.stage.FileChooser;
|
||||||
|
import javafx.util.Duration;
|
||||||
|
|
||||||
|
import envoy.client.data.*;
|
||||||
|
import envoy.client.data.audio.AudioRecorder;
|
||||||
|
import envoy.client.event.MessageCreationEvent;
|
||||||
|
import envoy.client.net.Client;
|
||||||
|
import envoy.client.net.WriteProxy;
|
||||||
|
import envoy.client.ui.IconUtil;
|
||||||
|
import envoy.client.ui.Restorable;
|
||||||
|
import envoy.client.ui.SceneContext;
|
||||||
|
import envoy.client.ui.listcell.ContactListCellFactory;
|
||||||
|
import envoy.client.ui.listcell.MessageControl;
|
||||||
|
import envoy.client.ui.listcell.MessageListCellFactory;
|
||||||
|
import envoy.data.*;
|
||||||
|
import envoy.data.Attachment.AttachmentType;
|
||||||
|
import envoy.event.*;
|
||||||
|
import envoy.event.contact.ContactOperation;
|
||||||
|
import envoy.exception.EnvoyException;
|
||||||
|
import envoy.util.EnvoyLog;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Project: <strong>envoy-client</strong><br>
|
||||||
|
* File: <strong>ChatSceneController.java</strong><br>
|
||||||
|
* Created: <strong>26.03.2020</strong><br>
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public final class ChatScene implements Restorable {
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private GridPane scene;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Label contactLabel;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private ListView<Message> messageList;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private ListView<Chat> chatList;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Button postButton;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Button voiceButton;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Button attachmentButton;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Button settingsButton;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Button rotateButton;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private TextArea messageTextArea;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Label remainingChars;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Label infoLabel;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private MenuItem deleteContactMenuItem;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private ImageView attachmentView;
|
||||||
|
|
||||||
|
private LocalDB localDB;
|
||||||
|
private Client client;
|
||||||
|
private WriteProxy writeProxy;
|
||||||
|
private SceneContext sceneContext;
|
||||||
|
|
||||||
|
private Chat currentChat;
|
||||||
|
private AudioRecorder recorder;
|
||||||
|
private boolean recording;
|
||||||
|
private Attachment pendingAttachment;
|
||||||
|
private boolean postingPermanentlyDisabled;
|
||||||
|
|
||||||
|
private static final Settings settings = Settings.getInstance();
|
||||||
|
private static final EventBus eventBus = EventBus.getInstance();
|
||||||
|
private static final Logger logger = EnvoyLog.getLogger(ChatScene.class);
|
||||||
|
|
||||||
|
private static final Image DEFAULT_ATTACHMENT_VIEW_IMAGE = IconUtil.loadIconThemeSensitive("attachment_present", 20);
|
||||||
|
private static final int MAX_MESSAGE_LENGTH = 255;
|
||||||
|
private static final int DEFAULT_ICON_SIZE = 16;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the appearance of certain visual components.
|
||||||
|
*
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
@FXML
|
||||||
|
private void initialize() {
|
||||||
|
|
||||||
|
// Initialize message and user rendering
|
||||||
|
messageList.setCellFactory(MessageListCellFactory::new);
|
||||||
|
chatList.setCellFactory(ContactListCellFactory::new);
|
||||||
|
|
||||||
|
settingsButton.setGraphic(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)));
|
||||||
|
attachmentView.setImage(DEFAULT_ATTACHMENT_VIEW_IMAGE);
|
||||||
|
rotateButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("rotate", (int) (DEFAULT_ICON_SIZE * 1.5))));
|
||||||
|
|
||||||
|
// Listen to received messages
|
||||||
|
eventBus.register(MessageCreationEvent.class, e -> {
|
||||||
|
final var message = e.get();
|
||||||
|
localDB.getChat(message instanceof GroupMessage ? message.getRecipientID() : message.getSenderID()).ifPresent(chat -> {
|
||||||
|
chat.insert(message);
|
||||||
|
if (chat.equals(currentChat)) {
|
||||||
|
try {
|
||||||
|
currentChat.read(writeProxy);
|
||||||
|
} catch (final IOException e1) {
|
||||||
|
logger.log(Level.WARNING, "Could not read current chat: ", e1);
|
||||||
|
}
|
||||||
|
Platform.runLater(() -> { messageList.refresh(); scrollToMessageListEnd(); });
|
||||||
|
} else chat.incrementUnreadAmount();
|
||||||
|
// Moving chat with most recent unreadMessages to the top
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
chatList.getItems().remove(chat);
|
||||||
|
chatList.getItems().add(0, chat);
|
||||||
|
if (chat.equals(currentChat)) chatList.getSelectionModel().select(0);
|
||||||
|
localDB.getChats().remove(chat);
|
||||||
|
localDB.getChats().add(0, chat);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Listen to message status changes
|
||||||
|
eventBus.register(MessageStatusChange.class, e -> localDB.getMessage(e.getID()).ifPresent(message -> {
|
||||||
|
message.setStatus(e.get());
|
||||||
|
// Update UI if in current chat
|
||||||
|
if (currentChat != null && message.getSenderID() == currentChat.getRecipient().getID()) Platform.runLater(messageList::refresh);
|
||||||
|
}));
|
||||||
|
|
||||||
|
eventBus.register(GroupMessageStatusChange.class, e -> localDB.getMessage(e.getID()).ifPresent(groupMessage -> {
|
||||||
|
((GroupMessage) groupMessage).getMemberStatuses().replace(e.getMemberID(), e.get());
|
||||||
|
|
||||||
|
// Update UI if in current chat
|
||||||
|
if (currentChat != null && groupMessage.getRecipientID() == currentChat.getRecipient().getID()) Platform.runLater(messageList::refresh);
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Listen to user status changes
|
||||||
|
eventBus.register(UserStatusChange.class,
|
||||||
|
e -> chatList.getItems()
|
||||||
|
.stream()
|
||||||
|
.filter(c -> c.getRecipient().getID() == e.getID())
|
||||||
|
.findAny()
|
||||||
|
.map(Chat::getRecipient)
|
||||||
|
.ifPresent(u -> { ((User) u).setStatus(e.get()); Platform.runLater(chatList::refresh); }));
|
||||||
|
|
||||||
|
// Listen to contacts changes
|
||||||
|
eventBus.register(ContactOperation.class, e -> {
|
||||||
|
final var contact = e.get();
|
||||||
|
switch (e.getOperationType()) {
|
||||||
|
case ADD:
|
||||||
|
localDB.getUsers().put(contact.getName(), contact);
|
||||||
|
Chat chat = contact instanceof User ? new Chat(contact) : new GroupChat(client.getSender(), contact);
|
||||||
|
localDB.getChats().add(chat);
|
||||||
|
Platform.runLater(() -> chatList.getItems().add(chat));
|
||||||
|
break;
|
||||||
|
case REMOVE:
|
||||||
|
localDB.getUsers().remove(contact.getName());
|
||||||
|
localDB.getChats().removeIf(c -> c.getRecipient().getID() == contact.getID());
|
||||||
|
Platform.runLater(() -> chatList.getItems().removeIf(c -> c.getRecipient().getID() == contact.getID()));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes all necessary data via dependency injection-
|
||||||
|
*
|
||||||
|
* @param sceneContext the scene context used to load other scenes
|
||||||
|
* @param localDB the local database form which chats and users are loaded
|
||||||
|
* @param client the client used to request ID generators
|
||||||
|
* @param writeProxy the write proxy used to send messages and other data to
|
||||||
|
* the server
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public void initializeData(SceneContext sceneContext, LocalDB localDB, Client client, WriteProxy writeProxy) {
|
||||||
|
this.sceneContext = sceneContext;
|
||||||
|
this.localDB = localDB;
|
||||||
|
this.client = client;
|
||||||
|
this.writeProxy = writeProxy;
|
||||||
|
|
||||||
|
chatList.setItems(FXCollections.observableList(localDB.getChats()));
|
||||||
|
contactLabel.setText(localDB.getUser().getName());
|
||||||
|
MessageControl.setUser(localDB.getUser());
|
||||||
|
if (!client.isOnline()) updateInfoLabel("You are offline", "infoLabel-info");
|
||||||
|
|
||||||
|
recorder = new AudioRecorder();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRestore() { updateRemainingCharsLabel(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Actions to perform when the list of contacts has been clicked.
|
||||||
|
*
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
@FXML
|
||||||
|
private void chatListClicked() {
|
||||||
|
final Contact user = chatList.getSelectionModel().getSelectedItem().getRecipient();
|
||||||
|
if (user != null && (currentChat == null || !user.equals(currentChat.getRecipient()))) {
|
||||||
|
|
||||||
|
// LEON: JFC <===> JAVA FRIED CHICKEN <=/=> Java Foundation Classes
|
||||||
|
|
||||||
|
// Load the chat
|
||||||
|
currentChat = localDB.getChat(user.getID()).get();
|
||||||
|
|
||||||
|
messageList.setItems(FXCollections.observableList(currentChat.getMessages()));
|
||||||
|
final var scrollIndex = messageList.getItems().size() - currentChat.getUnreadAmount() - 1;
|
||||||
|
messageList.scrollTo(scrollIndex);
|
||||||
|
logger.log(Level.FINEST, "Loading chat with " + user + " at index " + scrollIndex);
|
||||||
|
deleteContactMenuItem.setText("Delete " + user.getName());
|
||||||
|
|
||||||
|
// Read the current chat
|
||||||
|
try {
|
||||||
|
currentChat.read(writeProxy);
|
||||||
|
} catch (final IOException e) {
|
||||||
|
logger.log(Level.WARNING, "Could not read current chat.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Discard the pending attachment
|
||||||
|
if (recorder.isRecording()) {
|
||||||
|
recorder.cancel();
|
||||||
|
recording = false;
|
||||||
|
}
|
||||||
|
pendingAttachment = null;
|
||||||
|
updateAttachmentView(false);
|
||||||
|
|
||||||
|
remainingChars.setVisible(true);
|
||||||
|
remainingChars
|
||||||
|
.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());
|
||||||
|
attachmentButton.setDisable(false);
|
||||||
|
chatList.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Actions to perform when the Settings Button has been clicked.
|
||||||
|
*
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
@FXML
|
||||||
|
private void settingsButtonClicked() {
|
||||||
|
sceneContext.load(SceneContext.SceneInfo.SETTINGS_SCENE);
|
||||||
|
sceneContext.<SettingsScene>getController().initializeData(sceneContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Actions to perform when the "Add Contact" - Button has been clicked.
|
||||||
|
*
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
@FXML
|
||||||
|
private void addContactButtonClicked() {
|
||||||
|
sceneContext.load(SceneContext.SceneInfo.CONTACT_SEARCH_SCENE);
|
||||||
|
sceneContext.<ContactSearchScene>getController().initializeData(sceneContext, localDB);
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private void voiceButtonClicked() {
|
||||||
|
new Thread(() -> {
|
||||||
|
try {
|
||||||
|
if (!recording) {
|
||||||
|
recording = true;
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
voiceButton.setText("Recording");
|
||||||
|
voiceButton.setGraphic(new ImageView(IconUtil.loadIcon("microphone_recording", DEFAULT_ICON_SIZE)));
|
||||||
|
});
|
||||||
|
recorder.start();
|
||||||
|
} else {
|
||||||
|
pendingAttachment = new Attachment(recorder.finish(), AttachmentType.VOICE);
|
||||||
|
recording = false;
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
voiceButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("microphone", DEFAULT_ICON_SIZE)));
|
||||||
|
voiceButton.setText(null);
|
||||||
|
checkPostConditions(false);
|
||||||
|
updateAttachmentView(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (final EnvoyException e) {
|
||||||
|
logger.log(Level.SEVERE, "Could not record audio: ", e);
|
||||||
|
Platform.runLater(new Alert(AlertType.ERROR, "Could not record audio")::showAndWait);
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private void attachmentButtonClicked() {
|
||||||
|
|
||||||
|
// Display file chooser
|
||||||
|
final var fileChooser = new FileChooser();
|
||||||
|
fileChooser.setTitle("Add Attachment");
|
||||||
|
fileChooser.setInitialDirectory(new File(System.getProperty("user.home")));
|
||||||
|
fileChooser.getExtensionFilters()
|
||||||
|
.addAll(new FileChooser.ExtensionFilter("Pictures", "*.png", "*.jpg", "*.bmp", "*.gif"),
|
||||||
|
new FileChooser.ExtensionFilter("Videos", "*.mp4"),
|
||||||
|
new FileChooser.ExtensionFilter("All Files", "*.*"));
|
||||||
|
final var file = fileChooser.showOpenDialog(sceneContext.getStage());
|
||||||
|
|
||||||
|
if (file != null) {
|
||||||
|
|
||||||
|
// Check max file size
|
||||||
|
if (file.length() > 16E6) {
|
||||||
|
new Alert(AlertType.WARNING, "The selected file exceeds the size limit of 16MB!").showAndWait();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get attachment type (default is document)
|
||||||
|
AttachmentType type = AttachmentType.DOCUMENT;
|
||||||
|
switch (fileChooser.getSelectedExtensionFilter().getDescription()) {
|
||||||
|
case "Pictures":
|
||||||
|
type = AttachmentType.PICTURE;
|
||||||
|
break;
|
||||||
|
case "Videos":
|
||||||
|
type = AttachmentType.VIDEO;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the pending attachment
|
||||||
|
try {
|
||||||
|
final var fileBytes = Files.readAllBytes(file.toPath());
|
||||||
|
pendingAttachment = new Attachment(fileBytes, type);
|
||||||
|
// Setting the preview image as image of the attachmentView
|
||||||
|
if (type == AttachmentType.PICTURE)
|
||||||
|
attachmentView.setImage(new Image(new ByteArrayInputStream(fileBytes), DEFAULT_ICON_SIZE, DEFAULT_ICON_SIZE, true, true));
|
||||||
|
attachmentView.setVisible(true);
|
||||||
|
} catch (final IOException e) {
|
||||||
|
new Alert(AlertType.ERROR, "The selected file could not be loaded!").showAndWait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rotates every element in our application by 360° in at most 2.75s.
|
||||||
|
*
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
@FXML
|
||||||
|
private void doABarrelRoll() {
|
||||||
|
// contains all Node objects in ChatScene in alphabetical order
|
||||||
|
final var rotatableNodes = new Node[] { attachmentButton, attachmentView, contactLabel, infoLabel, messageList, messageTextArea,
|
||||||
|
postButton, remainingChars, rotateButton, scene, settingsButton, userList, voiceButton };
|
||||||
|
final var random = new Random();
|
||||||
|
for (final var node : rotatableNodes) {
|
||||||
|
// Defines at most four whole rotation in at most 4s
|
||||||
|
final var rotateTransition = new RotateTransition(Duration.seconds(random.nextDouble() * 3 + 1), node);
|
||||||
|
rotateTransition.setByAngle((random.nextInt(3) + 1) * 360);
|
||||||
|
rotateTransition.play();
|
||||||
|
// This is needed as for some strange reason objects could stop before being
|
||||||
|
// rotated back to 0°
|
||||||
|
rotateTransition.setOnFinished(e -> node.setRotate(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks the text length of the {@code messageTextArea}, adjusts the
|
||||||
|
* {@code remainingChars} label and checks whether to send the message
|
||||||
|
* automatically.
|
||||||
|
*
|
||||||
|
* @param e the key event that will be analyzed for a post request
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
@FXML
|
||||||
|
private void checkKeyCombination(KeyEvent e) {
|
||||||
|
// Checks whether the text is too long
|
||||||
|
messageTextUpdated();
|
||||||
|
// Automatic sending of messages via (ctrl +) enter
|
||||||
|
checkPostConditions(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param e the keys that have been pressed
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
@FXML
|
||||||
|
private void checkPostConditions(KeyEvent e) {
|
||||||
|
checkPostConditions(settings.isEnterToSend() && e.getCode() == KeyCode.ENTER
|
||||||
|
|| !settings.isEnterToSend() && e.getCode() == KeyCode.ENTER && e.isControlDown());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkPostConditions(boolean sendKeyPressed) {
|
||||||
|
if (!postingPermanentlyDisabled) {
|
||||||
|
if (!postButton.isDisabled() && sendKeyPressed) postMessage();
|
||||||
|
postButton.setDisable(messageTextArea.getText().isBlank() && pendingAttachment == null || currentChat == null);
|
||||||
|
} else {
|
||||||
|
final var noMoreMessaging = "Go online to send messages";
|
||||||
|
if (!infoLabel.getText().equals(noMoreMessaging))
|
||||||
|
// Informing the user that he is a f*cking moron and should use Envoy online
|
||||||
|
// because he ran out of messageIDs to use
|
||||||
|
updateInfoLabel(noMoreMessaging, "infoLabel-error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Actions to perform when the text was updated in the messageTextArea.
|
||||||
|
*
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
@FXML
|
||||||
|
private void messageTextUpdated() {
|
||||||
|
// Truncating messages that are too long and staying at the same position
|
||||||
|
if (messageTextArea.getText().length() >= MAX_MESSAGE_LENGTH) {
|
||||||
|
messageTextArea.setText(messageTextArea.getText().substring(0, MAX_MESSAGE_LENGTH));
|
||||||
|
messageTextArea.positionCaret(MAX_MESSAGE_LENGTH);
|
||||||
|
messageTextArea.setScrollTop(Double.MAX_VALUE);
|
||||||
|
}
|
||||||
|
updateRemainingCharsLabel();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the text and text color of the {@code remainingChars} label.
|
||||||
|
*
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
private void updateRemainingCharsLabel() {
|
||||||
|
final int currentLength = messageTextArea.getText().length();
|
||||||
|
final int remainingLength = MAX_MESSAGE_LENGTH - currentLength;
|
||||||
|
remainingChars.setText(String.format("remaining chars: %d/%d", remainingLength, MAX_MESSAGE_LENGTH));
|
||||||
|
remainingChars.setTextFill(Color.rgb(currentLength, remainingLength, 0, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a new {@link Message} or {@link GroupMessage} to the server based on
|
||||||
|
* the text entered in the {@code messageTextArea} and the given attachment.
|
||||||
|
*
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
@FXML
|
||||||
|
private void postMessage() {
|
||||||
|
postingPermanentlyDisabled = !(client.isOnline() || localDB.getIDGenerator().hasNext());
|
||||||
|
if (postingPermanentlyDisabled) {
|
||||||
|
postButton.setDisable(true);
|
||||||
|
messageTextArea.setDisable(true);
|
||||||
|
messageTextArea.clear();
|
||||||
|
updateInfoLabel("You need to go online to send more messages", "infoLabel-error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final var text = messageTextArea.getText().strip();
|
||||||
|
try {
|
||||||
|
// Creating the message and its metadata
|
||||||
|
final var builder = new MessageBuilder(localDB.getUser().getID(), currentChat.getRecipient().getID(), localDB.getIDGenerator())
|
||||||
|
.setText(text);
|
||||||
|
// Setting an attachment, if present
|
||||||
|
if (pendingAttachment != null) {
|
||||||
|
builder.setAttachment(pendingAttachment);
|
||||||
|
pendingAttachment = null;
|
||||||
|
updateAttachmentView(false);
|
||||||
|
}
|
||||||
|
// Building the final message
|
||||||
|
final var message = currentChat.getRecipient() instanceof Group ? builder.buildGroupMessage((Group) currentChat.getRecipient())
|
||||||
|
: builder.build();
|
||||||
|
|
||||||
|
// Send message
|
||||||
|
writeProxy.writeMessage(message);
|
||||||
|
|
||||||
|
// Add message to LocalDB and update UI
|
||||||
|
currentChat.insert(message);
|
||||||
|
// Moving currentChat to the top
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
chatList.getItems().remove(currentChat);
|
||||||
|
chatList.getItems().add(0, currentChat);
|
||||||
|
chatList.getSelectionModel().select(0);
|
||||||
|
localDB.getChats().remove(currentChat);
|
||||||
|
localDB.getChats().add(0, currentChat);
|
||||||
|
});
|
||||||
|
messageList.refresh();
|
||||||
|
scrollToMessageListEnd();
|
||||||
|
|
||||||
|
// Request a new ID generator if all IDs were used
|
||||||
|
if (!localDB.getIDGenerator().hasNext() && client.isOnline()) client.requestIdGenerator();
|
||||||
|
|
||||||
|
} catch (final IOException e) {
|
||||||
|
logger.log(Level.SEVERE, "Error while sending message: ", e);
|
||||||
|
new Alert(AlertType.ERROR, "An error occured while sending the message!").showAndWait();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear text field and disable post button
|
||||||
|
messageTextArea.setText("");
|
||||||
|
postButton.setDisable(true);
|
||||||
|
updateRemainingCharsLabel();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scrolls to the bottom of the {@code messageList}.
|
||||||
|
*
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
private void scrollToMessageListEnd() { messageList.scrollTo(messageList.getItems().size() - 1); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the {@code infoLabel}.
|
||||||
|
*
|
||||||
|
* @param text the text to use
|
||||||
|
* @param infoLabelID the id the the {@code infoLabel} should have so that it
|
||||||
|
* can be styled accordingly in CSS
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
private void updateInfoLabel(String text, String infoLabelID) {
|
||||||
|
infoLabel.setText(text);
|
||||||
|
infoLabel.setId(infoLabelID);
|
||||||
|
infoLabel.setVisible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the {@code attachmentView} in terms of visibility.<br>
|
||||||
|
* Additionally resets the shown image to
|
||||||
|
* {@code DEFAULT_ATTACHMENT_VIEW_IMAGE} if another image is currently
|
||||||
|
* present.
|
||||||
|
*
|
||||||
|
* @param visible whether the {@code attachmentView} should be displayed
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
private void updateAttachmentView(boolean visible) {
|
||||||
|
if (!attachmentView.getImage().equals(DEFAULT_ATTACHMENT_VIEW_IMAGE)) attachmentView.setImage(DEFAULT_ATTACHMENT_VIEW_IMAGE);
|
||||||
|
attachmentView.setVisible(visible);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Context menu actions
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private void deleteContact() { try {} catch (final NullPointerException e) {} }
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private void copyAndPostMessage() {
|
||||||
|
final var messageText = messageTextArea.getText();
|
||||||
|
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(messageText), null);
|
||||||
|
postMessage();
|
||||||
|
messageTextArea.setText(messageText);
|
||||||
|
updateRemainingCharsLabel();
|
||||||
|
postButton.setDisable(messageText.isBlank());
|
||||||
|
}
|
||||||
|
}
|
130
src/main/java/envoy/client/ui/controller/ContactSearchScene.java
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
package envoy.client.ui.controller;
|
||||||
|
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.scene.control.Alert;
|
||||||
|
import javafx.scene.control.Alert.AlertType;
|
||||||
|
import javafx.scene.control.ButtonType;
|
||||||
|
import javafx.scene.control.ListView;
|
||||||
|
|
||||||
|
import envoy.client.data.Chat;
|
||||||
|
import envoy.client.data.LocalDB;
|
||||||
|
import envoy.client.event.SendEvent;
|
||||||
|
import envoy.client.ui.ClearableTextField;
|
||||||
|
import envoy.client.ui.SceneContext;
|
||||||
|
import envoy.client.ui.listcell.ContactListCellFactory;
|
||||||
|
import envoy.event.ElementOperation;
|
||||||
|
import envoy.event.EventBus;
|
||||||
|
import envoy.event.contact.ContactOperation;
|
||||||
|
import envoy.event.contact.ContactSearchRequest;
|
||||||
|
import envoy.event.contact.ContactSearchResult;
|
||||||
|
import envoy.util.EnvoyLog;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Project: <strong>envoy-client</strong><br>
|
||||||
|
* File: <strong>ContactSearchSceneController.java</strong><br>
|
||||||
|
* Created: <strong>07.06.2020</strong><br>
|
||||||
|
*
|
||||||
|
* @author Leon Hofmeister
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public class ContactSearchScene {
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private ClearableTextField searchBar;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private ListView<Chat> chatList;
|
||||||
|
|
||||||
|
private SceneContext sceneContext;
|
||||||
|
|
||||||
|
private LocalDB localDB;
|
||||||
|
|
||||||
|
private static EventBus eventBus = EventBus.getInstance();
|
||||||
|
private static final Logger logger = EnvoyLog.getLogger(ChatScene.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param sceneContext enables the user to return to the chat scene
|
||||||
|
* @param localDB the local database to which new contacts are added
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public void initializeData(SceneContext sceneContext, LocalDB localDB) {
|
||||||
|
this.sceneContext = sceneContext;
|
||||||
|
this.localDB = localDB;
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private void initialize() {
|
||||||
|
chatList.setCellFactory(ContactListCellFactory::new);
|
||||||
|
searchBar.setClearButtonListener(e -> { searchBar.getTextField().clear(); chatList.getItems().clear(); });
|
||||||
|
eventBus.register(ContactSearchResult.class,
|
||||||
|
response -> Platform.runLater(() -> {
|
||||||
|
chatList.getItems().clear();
|
||||||
|
chatList.getItems().addAll(response.get().stream().map(Chat::new).collect(Collectors.toList()));
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disables the clear and search button if no text is present in the search bar.
|
||||||
|
*
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
@FXML
|
||||||
|
private void sendRequest() {
|
||||||
|
final var text = searchBar.getTextField().getText().strip();
|
||||||
|
if (!text.isBlank()) eventBus.dispatch(new SendEvent(new ContactSearchRequest(text)));
|
||||||
|
else chatList.getItems().clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the text in the search bar and the items shown in the list.
|
||||||
|
* Additionally disables both clear and search button.
|
||||||
|
*
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
@FXML
|
||||||
|
private void clear() {
|
||||||
|
searchBar.getTextField().setText(null);
|
||||||
|
chatList.getItems().clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends an {@link ContactOperation} for every selected contact to the
|
||||||
|
* server.
|
||||||
|
*
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
@FXML
|
||||||
|
private void chatListClicked() {
|
||||||
|
final var chat = chatList.getSelectionModel().getSelectedItem();
|
||||||
|
if (chat != null) {
|
||||||
|
final var alert = new Alert(AlertType.CONFIRMATION);
|
||||||
|
alert.setTitle("Add Contact to Contact List");
|
||||||
|
alert.setHeaderText("Add the user " + chat.getRecipient().getName() + " to your contact list?");
|
||||||
|
// Normally, this would be total BS (we are already on the FX Thread), however
|
||||||
|
// it could be proven that the creation of this dialog wrapped in
|
||||||
|
// Platform.runLater is less error-prone than without it
|
||||||
|
Platform.runLater(() -> alert.showAndWait().filter(btn -> btn == ButtonType.OK).ifPresent(btn -> {
|
||||||
|
final var event = new ContactOperation(chat.getRecipient(), ElementOperation.ADD);
|
||||||
|
// Sends the event to the server
|
||||||
|
eventBus.dispatch(new SendEvent(event));
|
||||||
|
// Updates the UI
|
||||||
|
eventBus.dispatch(event);
|
||||||
|
logger.log(Level.INFO, "Added contact " + chat.getRecipient());
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private void newGroupButtonClicked() {
|
||||||
|
sceneContext.load(SceneContext.SceneInfo.GROUP_CREATION_SCENE);
|
||||||
|
sceneContext.<GroupCreationScene>getController().initializeData(sceneContext, localDB);
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private void backButtonClicked() { sceneContext.pop(); }
|
||||||
|
}
|
109
src/main/java/envoy/client/ui/controller/GroupCreationScene.java
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
package envoy.client.ui.controller;
|
||||||
|
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.scene.control.*;
|
||||||
|
import javafx.scene.control.Alert.AlertType;
|
||||||
|
|
||||||
|
import envoy.client.data.Chat;
|
||||||
|
import envoy.client.data.LocalDB;
|
||||||
|
import envoy.client.event.SendEvent;
|
||||||
|
import envoy.client.ui.ClearableTextField;
|
||||||
|
import envoy.client.ui.SceneContext;
|
||||||
|
import envoy.client.ui.listcell.ContactListCellFactory;
|
||||||
|
import envoy.data.Group;
|
||||||
|
import envoy.event.EventBus;
|
||||||
|
import envoy.event.GroupCreation;
|
||||||
|
import envoy.util.Bounds;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Project: <strong>envoy-client</strong><br>
|
||||||
|
* File: <strong>ContactSearchSceneController.java</strong><br>
|
||||||
|
* Created: <strong>07.06.2020</strong><br>
|
||||||
|
*
|
||||||
|
* @author Maximilian Käfer
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public class GroupCreationScene {
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Button createButton;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private ClearableTextField groupNameField;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private ListView<Chat> chatList;
|
||||||
|
|
||||||
|
private SceneContext sceneContext;
|
||||||
|
|
||||||
|
private static final EventBus eventBus = EventBus.getInstance();
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private void initialize() {
|
||||||
|
chatList.setCellFactory(ContactListCellFactory::new);
|
||||||
|
chatList.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
|
||||||
|
groupNameField.setClearButtonListener(e -> { groupNameField.getTextField().clear(); createButton.setDisable(true); });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param sceneContext enables the user to return to the chat scene
|
||||||
|
* @param localDB the local database from which potential group members can
|
||||||
|
* be selected
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public void initializeData(SceneContext sceneContext, LocalDB localDB) {
|
||||||
|
this.sceneContext = sceneContext;
|
||||||
|
Platform.runLater(() -> chatList.getItems()
|
||||||
|
.addAll(localDB.getChats()
|
||||||
|
.stream()
|
||||||
|
.filter(c -> !(c.getRecipient() instanceof Group))
|
||||||
|
.filter(c -> c.getRecipient().getID() != localDB.getUser().getID())
|
||||||
|
.collect(Collectors.toList())));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables the {@code createButton} if at least one contact is selected.
|
||||||
|
*
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
@FXML
|
||||||
|
private void chatListClicked() {
|
||||||
|
createButton.setDisable(chatList.getSelectionModel().isEmpty() || groupNameField.getTextField().getText().isBlank());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks, whether the {@code createButton} can be enabled because text is
|
||||||
|
* present in the textfield.
|
||||||
|
*
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
@FXML
|
||||||
|
private void textUpdated() { createButton.setDisable(groupNameField.getTextField().getText().isBlank()); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a {@link GroupCreation} to the server and closes this scene.
|
||||||
|
* <p>
|
||||||
|
* If the given group name is not valid, an error is displayed instead.
|
||||||
|
*
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
@FXML
|
||||||
|
private void createButtonClicked() {
|
||||||
|
final var name = groupNameField.getTextField().getText();
|
||||||
|
if (!Bounds.isValidContactName(name)) {
|
||||||
|
new Alert(AlertType.ERROR, "The entered group name is not valid (" + Bounds.CONTACT_NAME_PATTERN + ")").showAndWait();
|
||||||
|
groupNameField.getTextField().clear();
|
||||||
|
} else {
|
||||||
|
eventBus.dispatch(new SendEvent(new GroupCreation(name,
|
||||||
|
chatList.getSelectionModel().getSelectedItems().stream().map(c -> c.getRecipient().getID()).collect(Collectors.toSet()))));
|
||||||
|
new Alert(AlertType.INFORMATION, String.format("Group '%s' successfully created.", name)).showAndWait();
|
||||||
|
sceneContext.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private void backButtonClicked() { sceneContext.pop(); }
|
||||||
|
}
|
199
src/main/java/envoy/client/ui/controller/LoginScene.java
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
package envoy.client.ui.controller;
|
||||||
|
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.scene.control.*;
|
||||||
|
import javafx.scene.control.Alert.AlertType;
|
||||||
|
|
||||||
|
import envoy.client.data.*;
|
||||||
|
import envoy.client.net.Client;
|
||||||
|
import envoy.client.net.WriteProxy;
|
||||||
|
import envoy.client.ui.ClearableTextField;
|
||||||
|
import envoy.client.ui.SceneContext;
|
||||||
|
import envoy.client.ui.Startup;
|
||||||
|
import envoy.data.LoginCredentials;
|
||||||
|
import envoy.data.User;
|
||||||
|
import envoy.data.User.UserStatus;
|
||||||
|
import envoy.event.EventBus;
|
||||||
|
import envoy.event.HandshakeRejection;
|
||||||
|
import envoy.exception.EnvoyException;
|
||||||
|
import envoy.util.Bounds;
|
||||||
|
import envoy.util.EnvoyLog;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Project: <strong>envoy-client</strong><br>
|
||||||
|
* File: <strong>LoginDialog.java</strong><br>
|
||||||
|
* Created: <strong>03.04.2020</strong><br>
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @author Maximilian Käfer
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public final class LoginScene {
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private ClearableTextField userTextField;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private PasswordField passwordField;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private PasswordField repeatPasswordField;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Label repeatPasswordLabel;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private CheckBox registerCheckBox;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Label connectionLabel;
|
||||||
|
|
||||||
|
private Client client;
|
||||||
|
private LocalDB localDB;
|
||||||
|
private CacheMap cacheMap;
|
||||||
|
private SceneContext sceneContext;
|
||||||
|
|
||||||
|
private static final Logger logger = EnvoyLog.getLogger(LoginScene.class);
|
||||||
|
private static final EventBus eventBus = EventBus.getInstance();
|
||||||
|
private static final ClientConfig config = ClientConfig.getInstance();
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private void initialize() {
|
||||||
|
connectionLabel.setText("Server: " + config.getServer() + ":" + config.getPort());
|
||||||
|
|
||||||
|
// Show an alert after an unsuccessful handshake
|
||||||
|
eventBus.register(HandshakeRejection.class, e -> Platform.runLater(() -> { new Alert(AlertType.ERROR, e.get()).showAndWait(); }));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the login dialog using the FXML file {@code LoginDialog.fxml}.
|
||||||
|
*
|
||||||
|
* @param client the client used to perform the handshake
|
||||||
|
* @param localDB the local database used for offline login
|
||||||
|
* @param cacheMap the map of all caches needed
|
||||||
|
* @param sceneContext the scene context used to initialize the chat scene
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public void initializeData(Client client, LocalDB localDB, CacheMap cacheMap, SceneContext sceneContext) {
|
||||||
|
this.client = client;
|
||||||
|
this.localDB = localDB;
|
||||||
|
this.cacheMap = cacheMap;
|
||||||
|
this.sceneContext = sceneContext;
|
||||||
|
|
||||||
|
// Prepare handshake
|
||||||
|
localDB.loadIDGenerator();
|
||||||
|
|
||||||
|
// Set initial cursor
|
||||||
|
userTextField.requestFocus();
|
||||||
|
|
||||||
|
// Perform automatic login if configured
|
||||||
|
if (config.hasLoginCredentials()) performHandshake(config.getLoginCredentials());
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private void loginButtonPressed() {
|
||||||
|
|
||||||
|
// Prevent registration with unequal passwords
|
||||||
|
if (registerCheckBox.isSelected() && !passwordField.getText().equals(repeatPasswordField.getText())) {
|
||||||
|
new Alert(AlertType.ERROR, "The entered password is unequal to the repeated one").showAndWait();
|
||||||
|
repeatPasswordField.clear();
|
||||||
|
} else if (!Bounds.isValidContactName(userTextField.getTextField().getText())) {
|
||||||
|
new Alert(AlertType.ERROR, "The entered user name is not valid (" + Bounds.CONTACT_NAME_PATTERN + ")").showAndWait();
|
||||||
|
userTextField.getTextField().clear();
|
||||||
|
} else performHandshake(new LoginCredentials(userTextField.getTextField().getText(), passwordField.getText(), registerCheckBox.isSelected(),
|
||||||
|
Startup.VERSION));
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private void offlineModeButtonPressed() {
|
||||||
|
attemptOfflineMode(new LoginCredentials(userTextField.getTextField().getText(), passwordField.getText(), false, Startup.VERSION));
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private void registerCheckboxChanged() {
|
||||||
|
|
||||||
|
// Make repeat password field and label visible / invisible
|
||||||
|
repeatPasswordField.setVisible(registerCheckBox.isSelected());
|
||||||
|
repeatPasswordLabel.setVisible(registerCheckBox.isSelected());
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private void abortLogin() {
|
||||||
|
logger.log(Level.INFO, "The login process has been cancelled. Exiting...");
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void performHandshake(LoginCredentials credentials) {
|
||||||
|
try {
|
||||||
|
client.performHandshake(credentials, cacheMap);
|
||||||
|
if (client.isOnline()) {
|
||||||
|
loadChatScene();
|
||||||
|
client.initReceiver(localDB, cacheMap);
|
||||||
|
}
|
||||||
|
} catch (IOException | InterruptedException | TimeoutException e) {
|
||||||
|
logger.log(Level.INFO, "Could not connect to server. Entering offline mode...");
|
||||||
|
attemptOfflineMode(credentials);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void attemptOfflineMode(LoginCredentials credentials) {
|
||||||
|
try {
|
||||||
|
// Try entering offline mode
|
||||||
|
localDB.loadUsers();
|
||||||
|
final User clientUser = (User) localDB.getUsers().get(credentials.getIdentifier());
|
||||||
|
if (clientUser == null) throw new EnvoyException("Could not enter offline mode: user name unknown");
|
||||||
|
client.setSender(clientUser);
|
||||||
|
loadChatScene();
|
||||||
|
} catch (final Exception e) {
|
||||||
|
new Alert(AlertType.ERROR, "Client error: " + e).showAndWait();
|
||||||
|
logger.log(Level.SEVERE, "Offline mode could not be loaded: ", e);
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadChatScene() {
|
||||||
|
|
||||||
|
// Set client user in local database
|
||||||
|
localDB.setUser(client.getSender());
|
||||||
|
|
||||||
|
// Initialize chats in local database
|
||||||
|
try {
|
||||||
|
localDB.initializeUserStorage();
|
||||||
|
localDB.loadUserData();
|
||||||
|
} catch (final FileNotFoundException e) {
|
||||||
|
// The local database file has not yet been created, probably first login
|
||||||
|
} catch (final Exception e) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize write proxy
|
||||||
|
final var writeProxy = new WriteProxy(client, localDB);
|
||||||
|
|
||||||
|
localDB.synchronize();
|
||||||
|
|
||||||
|
if (client.isOnline()) writeProxy.flushCache();
|
||||||
|
else
|
||||||
|
// Set all contacts to offline mode
|
||||||
|
localDB.getChats()
|
||||||
|
.stream()
|
||||||
|
.map(Chat::getRecipient)
|
||||||
|
.filter(User.class::isInstance)
|
||||||
|
.map(User.class::cast)
|
||||||
|
.forEach(u -> u.setStatus(UserStatus.OFFLINE));
|
||||||
|
|
||||||
|
// Load ChatScene
|
||||||
|
sceneContext.pop();
|
||||||
|
sceneContext.getStage().setMinHeight(400);
|
||||||
|
sceneContext.getStage().setMinWidth(350);
|
||||||
|
sceneContext.load(SceneContext.SceneInfo.CHAT_SCENE);
|
||||||
|
sceneContext.<ChatScene>getController().initializeData(sceneContext, localDB, client, writeProxy);
|
||||||
|
}
|
||||||
|
}
|
59
src/main/java/envoy/client/ui/controller/SettingsScene.java
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
package envoy.client.ui.controller;
|
||||||
|
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.scene.control.*;
|
||||||
|
|
||||||
|
import envoy.client.ui.SceneContext;
|
||||||
|
import envoy.client.ui.settings.GeneralSettingsPane;
|
||||||
|
import envoy.client.ui.settings.SettingsPane;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Project: <strong>envoy-client</strong><br>
|
||||||
|
* File: <strong>SettingsSceneController.java</strong><br>
|
||||||
|
* Created: <strong>10.04.2020</strong><br>
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public class SettingsScene {
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private ListView<SettingsPane> settingsList;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private TitledPane titledPane;
|
||||||
|
|
||||||
|
private SceneContext sceneContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param sceneContext enables the user to return to the chat scene
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public void initializeData(SceneContext sceneContext) { this.sceneContext = sceneContext; }
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private void initialize() {
|
||||||
|
settingsList.setCellFactory(listView -> new ListCell<>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void updateItem(SettingsPane item, boolean empty) {
|
||||||
|
super.updateItem(item, empty);
|
||||||
|
if (!empty && item != null) setGraphic(new Label(item.getTitle()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
settingsList.getItems().add(new GeneralSettingsPane());
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private void settingsListClicked() {
|
||||||
|
final var pane = settingsList.getSelectionModel().getSelectedItem();
|
||||||
|
if (pane != null) {
|
||||||
|
titledPane.setText(pane.getTitle());
|
||||||
|
titledPane.setContent(pane);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private void backButtonClicked() { sceneContext.pop(); }
|
||||||
|
}
|
11
src/main/java/envoy/client/ui/controller/package-info.java
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
/**
|
||||||
|
* Contains JavaFX scene controllers.
|
||||||
|
* <p>
|
||||||
|
* Project: <strong>envoy-client</strong><br>
|
||||||
|
* File: <strong>package-info.java</strong><br>
|
||||||
|
* Created: <strong>08.06.2020</strong><br>
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
package envoy.client.ui.controller;
|
58
src/main/java/envoy/client/ui/listcell/ChatControl.java
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package envoy.client.ui.listcell;
|
||||||
|
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.layout.*;
|
||||||
|
|
||||||
|
import envoy.client.data.Chat;
|
||||||
|
import envoy.data.Contact;
|
||||||
|
import envoy.data.Group;
|
||||||
|
import envoy.data.User;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class formats a single {@link Contact} into a UI component.
|
||||||
|
* <p>
|
||||||
|
* Project: <strong>envoy-client</strong><br>
|
||||||
|
* File: <strong>ContactControl.java</strong><br>
|
||||||
|
* Created: <strong>01.07.2020</strong><br>
|
||||||
|
*
|
||||||
|
* @author Leon Hofmeister
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public class ChatControl extends HBox {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param chat the chat to display
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public ChatControl(Chat chat) {
|
||||||
|
// Container with contact name
|
||||||
|
final var vBox = new VBox();
|
||||||
|
final var nameLabel = new Label(chat.getRecipient().getName());
|
||||||
|
nameLabel.setWrapText(true);
|
||||||
|
vBox.getChildren().add(nameLabel);
|
||||||
|
if (chat.getRecipient() instanceof User) {
|
||||||
|
// Online status
|
||||||
|
final var user = (User) chat.getRecipient();
|
||||||
|
final var statusLabel = new Label(user.getStatus().toString());
|
||||||
|
statusLabel.getStyleClass().add(user.getStatus().toString().toLowerCase());
|
||||||
|
vBox.getChildren().add(statusLabel);
|
||||||
|
} else // Member count
|
||||||
|
vBox.getChildren().add(new Label(((Group) chat.getRecipient()).getContacts().size() + " members"));
|
||||||
|
|
||||||
|
getChildren().add(vBox);
|
||||||
|
if (chat.getUnreadAmount() != 0) {
|
||||||
|
Region spacing = new Region();
|
||||||
|
setHgrow(spacing, Priority.ALWAYS);
|
||||||
|
getChildren().add(spacing);
|
||||||
|
final var unreadMessagesLabel = new Label(Integer.toString(chat.getUnreadAmount()));
|
||||||
|
unreadMessagesLabel.setMinSize(15, 15);
|
||||||
|
var vBox2 = new VBox();
|
||||||
|
vBox2.setAlignment(Pos.CENTER_RIGHT);
|
||||||
|
unreadMessagesLabel.setAlignment(Pos.CENTER);
|
||||||
|
unreadMessagesLabel.getStyleClass().add("unreadMessagesAmount");
|
||||||
|
vBox2.getChildren().add(unreadMessagesLabel);
|
||||||
|
getChildren().add(vBox2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
package envoy.client.ui.listcell;
|
||||||
|
|
||||||
|
import javafx.scene.control.ListCell;
|
||||||
|
import javafx.scene.control.ListView;
|
||||||
|
|
||||||
|
import envoy.client.data.Chat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Project: <strong>envoy-client</strong><br>
|
||||||
|
* File: <strong>UserListCell.java</strong><br>
|
||||||
|
* Created: <strong>28.03.2020</strong><br>
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public class ContactListCellFactory extends ListCell<Chat> {
|
||||||
|
|
||||||
|
private final ListView<Chat> listView;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param listView the list view inside which this cell is contained
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public ContactListCellFactory(ListView<Chat> listView) { this.listView = listView; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays the name of a contact. If the contact is a user, their online status
|
||||||
|
* is displayed as well.
|
||||||
|
*
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void updateItem(Chat chat, boolean empty) {
|
||||||
|
super.updateItem(chat, empty);
|
||||||
|
if (empty || chat.getRecipient() == null) {
|
||||||
|
setText(null);
|
||||||
|
setGraphic(null);
|
||||||
|
} else {
|
||||||
|
final var control = new ChatControl(chat);
|
||||||
|
prefWidthProperty().bind(listView.widthProperty().subtract(40));
|
||||||
|
setGraphic(control);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
123
src/main/java/envoy/client/ui/listcell/MessageControl.java
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
package envoy.client.ui.listcell;
|
||||||
|
|
||||||
|
import java.awt.Toolkit;
|
||||||
|
import java.awt.datatransfer.StringSelection;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javafx.geometry.Insets;
|
||||||
|
import javafx.scene.control.ContextMenu;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.control.MenuItem;
|
||||||
|
import javafx.scene.image.Image;
|
||||||
|
import javafx.scene.image.ImageView;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
|
||||||
|
import envoy.client.ui.AudioControl;
|
||||||
|
import envoy.client.ui.IconUtil;
|
||||||
|
import envoy.data.Message;
|
||||||
|
import envoy.data.Message.MessageStatus;
|
||||||
|
import envoy.data.User;
|
||||||
|
import envoy.util.EnvoyLog;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class formats a single {@link Message} into a UI component.
|
||||||
|
* <p>
|
||||||
|
* Project: <strong>envoy-client</strong><br>
|
||||||
|
* File: <strong>MessageControl.java</strong><br>
|
||||||
|
* Created: <strong>01.07.2020</strong><br>
|
||||||
|
*
|
||||||
|
* @author Leon Hofmeister
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public class MessageControl extends Label {
|
||||||
|
|
||||||
|
private static User client;
|
||||||
|
private static final DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm");
|
||||||
|
private static final Map<MessageStatus, Image> statusImages = IconUtil.loadByEnum(MessageStatus.class, 16);
|
||||||
|
|
||||||
|
private static final Logger logger = EnvoyLog.getLogger(MessageControl.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param message the message that should be formatted
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public MessageControl(Message message) {
|
||||||
|
// Creating the underlying VBox and the dateLabel
|
||||||
|
final var vbox = new VBox(new Label(dateFormat.format(message.getCreationDate())));
|
||||||
|
|
||||||
|
// Creating the actions for the MenuItems
|
||||||
|
final ContextMenu contextMenu = new ContextMenu();
|
||||||
|
final MenuItem copyMenuItem = new MenuItem("Copy");
|
||||||
|
final MenuItem deleteMenuItem = new MenuItem("Delete");
|
||||||
|
final MenuItem forwardMenuItem = new MenuItem("Forward");
|
||||||
|
final MenuItem quoteMenuItem = new MenuItem("Quote");
|
||||||
|
final MenuItem 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));
|
||||||
|
contextMenu.getItems().addAll(copyMenuItem, deleteMenuItem, forwardMenuItem, quoteMenuItem, infoMenuItem);
|
||||||
|
|
||||||
|
// Handling message attachment display
|
||||||
|
if (message.hasAttachment()) {
|
||||||
|
switch (message.getAttachment().getType()) {
|
||||||
|
case PICTURE:
|
||||||
|
vbox.getChildren().add(new ImageView(new Image(new ByteArrayInputStream(message.getAttachment().getData()), 256, 256, true, true)));
|
||||||
|
break;
|
||||||
|
case VIDEO:
|
||||||
|
break;
|
||||||
|
case VOICE:
|
||||||
|
vbox.getChildren().add(new AudioControl(message.getAttachment().getData()));
|
||||||
|
break;
|
||||||
|
case DOCUMENT:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
final var saveAttachment = new MenuItem("Save attachment");
|
||||||
|
saveAttachment.setOnAction(e -> saveAttachment(message));
|
||||||
|
contextMenu.getItems().add(saveAttachment);
|
||||||
|
}
|
||||||
|
// Creating the textLabel
|
||||||
|
final var textLabel = new Label(message.getText());
|
||||||
|
textLabel.setWrapText(true);
|
||||||
|
vbox.getChildren().add(textLabel);
|
||||||
|
// Setting the message status icon and background color
|
||||||
|
if (message.getSenderID() == client.getID()) {
|
||||||
|
final var statusIcon = new ImageView(statusImages.get(message.getStatus()));
|
||||||
|
statusIcon.setPreserveRatio(true);
|
||||||
|
vbox.getChildren().add(statusIcon);
|
||||||
|
getStyleClass().add("own-message");
|
||||||
|
} else getStyleClass().add("received-message");
|
||||||
|
// Adjusting height and weight of the cell to the corresponding ListView
|
||||||
|
paddingProperty().setValue(new Insets(5, 20, 5, 20));
|
||||||
|
setContextMenu(contextMenu);
|
||||||
|
setGraphic(vbox);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Context Menu actions
|
||||||
|
|
||||||
|
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) { logger.log(Level.FINEST, "attachment saving was requested for " + message); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param client the user who has logged in
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public static void setUser(User client) { MessageControl.client = client; }
|
||||||
|
}
|
@ -0,0 +1,52 @@
|
|||||||
|
package envoy.client.ui.listcell;
|
||||||
|
|
||||||
|
import javafx.scene.control.ListCell;
|
||||||
|
import javafx.scene.control.ListView;
|
||||||
|
import javafx.scene.control.Tooltip;
|
||||||
|
import javafx.stage.PopupWindow.AnchorLocation;
|
||||||
|
|
||||||
|
import envoy.data.Message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays a single message inside the message list.
|
||||||
|
* <p>
|
||||||
|
* Project: <strong>envoy-client</strong><br>
|
||||||
|
* File: <strong>MessageListCellFactory.java</strong><br>
|
||||||
|
* Created: <strong>28.03.2020</strong><br>
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public class MessageListCellFactory extends ListCell<Message> {
|
||||||
|
|
||||||
|
private final ListView<Message> listView;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param listView the list view inside which this cell is contained
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public MessageListCellFactory(ListView<Message> listView) { this.listView = listView; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays the text, the data of creation and the status of a message.
|
||||||
|
*
|
||||||
|
* @since Envoy v0.1-beta
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void updateItem(Message message, boolean empty) {
|
||||||
|
super.updateItem(message, empty);
|
||||||
|
if (empty || message == null) {
|
||||||
|
setText(null);
|
||||||
|
setGraphic(null);
|
||||||
|
} else {
|
||||||
|
final var control = new MessageControl(message);
|
||||||
|
control.prefWidthProperty().bind(listView.widthProperty().subtract(40));
|
||||||
|
// Creating the Tooltip to deselect a message
|
||||||
|
final var tooltip = new Tooltip("You can select a message by clicking on it \nand deselect it by pressing \"ctrl\" and clicking on it");
|
||||||
|
tooltip.setWrapText(true);
|
||||||
|
tooltip.setAnchorLocation(AnchorLocation.WINDOW_TOP_LEFT);
|
||||||
|
setTooltip(tooltip);
|
||||||
|
setGraphic(control);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
12
src/main/java/envoy/client/ui/listcell/package-info.java
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
/**
|
||||||
|
* This package contains custom list cells that are used to display certain
|
||||||
|
* things.
|
||||||
|
* <p>
|
||||||
|
* Project: <strong>envoy-client</strong><br>
|
||||||
|
* File: <strong>package-info.java</strong><br>
|
||||||
|
* Created: <strong>30.06.2020</strong><br>
|
||||||
|
*
|
||||||
|
* @author Leon Hofmeister
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
package envoy.client.ui.listcell;
|
9
src/main/java/envoy/client/ui/package-info.java
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
/**
|
||||||
|
* This package contains classes defining the user interface.
|
||||||
|
*
|
||||||
|
* @author Leon Hofmeister
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @author Maximilian Käfer
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
package envoy.client.ui;
|
@ -0,0 +1,57 @@
|
|||||||
|
package envoy.client.ui.settings;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javafx.scene.control.ComboBox;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
|
||||||
|
import envoy.client.data.Settings;
|
||||||
|
import envoy.client.data.SettingsItem;
|
||||||
|
import envoy.client.event.ThemeChangeEvent;
|
||||||
|
import envoy.data.User.UserStatus;
|
||||||
|
import envoy.event.EventBus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Project: <strong>envoy-client</strong><br>
|
||||||
|
* File: <strong>GeneralSettingsPane.java</strong><br>
|
||||||
|
* Created: <strong>18.04.2020</strong><br>
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public class GeneralSettingsPane extends SettingsPane {
|
||||||
|
|
||||||
|
private static final Settings settings = Settings.getInstance();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public GeneralSettingsPane() {
|
||||||
|
super("General");
|
||||||
|
final var vbox = new VBox();
|
||||||
|
|
||||||
|
// TODO: Support other value types
|
||||||
|
List.of("onCloseMode", "enterToSend")
|
||||||
|
.stream()
|
||||||
|
.map(settings.getItems()::get)
|
||||||
|
.map(i -> new SettingsCheckbox((SettingsItem<Boolean>) i))
|
||||||
|
.forEach(vbox.getChildren()::add);
|
||||||
|
|
||||||
|
final var combobox = new ComboBox<String>();
|
||||||
|
combobox.getItems().add("dark");
|
||||||
|
combobox.getItems().add("light");
|
||||||
|
combobox.setValue(settings.getCurrentTheme());
|
||||||
|
combobox.setOnAction(
|
||||||
|
e -> { settings.setCurrentTheme(combobox.getValue()); EventBus.getInstance().dispatch(new ThemeChangeEvent(combobox.getValue())); });
|
||||||
|
vbox.getChildren().add(combobox);
|
||||||
|
|
||||||
|
final var statusComboBox = new ComboBox<UserStatus>();
|
||||||
|
statusComboBox.getItems().setAll(UserStatus.values());
|
||||||
|
statusComboBox.setValue(UserStatus.ONLINE);
|
||||||
|
// TODO add action when value is changed
|
||||||
|
statusComboBox.setOnAction(e -> {});
|
||||||
|
vbox.getChildren().add(statusComboBox);
|
||||||
|
|
||||||
|
getChildren().add(vbox);
|
||||||
|
}
|
||||||
|
}
|
32
src/main/java/envoy/client/ui/settings/SettingsCheckbox.java
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package envoy.client.ui.settings;
|
||||||
|
|
||||||
|
import javafx.event.ActionEvent;
|
||||||
|
import javafx.scene.control.CheckBox;
|
||||||
|
|
||||||
|
import envoy.client.data.SettingsItem;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Project: <strong>envoy-client</strong><br>
|
||||||
|
* File: <strong>SettingsToggleButton.java</strong><br>
|
||||||
|
* Created: <strong>18.04.2020</strong><br>
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public final class SettingsCheckbox extends CheckBox {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance of {@link SettingsCheckbox}.
|
||||||
|
*
|
||||||
|
* @param settingsItem the {@link SettingsItem} whose values could be adapted
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public SettingsCheckbox(SettingsItem<Boolean> settingsItem) {
|
||||||
|
super(settingsItem.getUserFriendlyName());
|
||||||
|
setSelected(settingsItem.get());
|
||||||
|
|
||||||
|
// "Schau, es hat sich behindert" - Kai, 2020
|
||||||
|
|
||||||
|
addEventHandler(ActionEvent.ACTION, e -> settingsItem.set(isSelected()));
|
||||||
|
}
|
||||||
|
}
|
24
src/main/java/envoy/client/ui/settings/SettingsPane.java
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package envoy.client.ui.settings;
|
||||||
|
|
||||||
|
import javafx.scene.layout.Pane;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Project: <strong>envoy-client</strong><br>
|
||||||
|
* File: <strong>SettingsPane.java</strong><br>
|
||||||
|
* Created: <strong>18.04.2020</strong><br>
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public abstract class SettingsPane extends Pane {
|
||||||
|
|
||||||
|
protected String title;
|
||||||
|
|
||||||
|
protected SettingsPane(String title) { this.title = title; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the title of this settings pane
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public String getTitle() { return title; }
|
||||||
|
}
|
14
src/main/java/envoy/client/ui/settings/package-info.java
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
/**
|
||||||
|
* This package contains classes used for representing the settings
|
||||||
|
* visually.
|
||||||
|
* <p>
|
||||||
|
* Project: <strong>envoy-client</strong><br>
|
||||||
|
* File: <strong>package-info.java</strong><br>
|
||||||
|
* Created: <strong>19 Apr 2020</strong><br>
|
||||||
|
*
|
||||||
|
* @author Leon Hofmeister
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @author Maximilian Käfer
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
package envoy.client.ui.settings;
|
23
src/main/java/module-info.java
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* This module contains all classes defining the client application of the Envoy
|
||||||
|
* project.
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @author Leon Hofmeister
|
||||||
|
* @author Maximilian Käfer
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
module envoy {
|
||||||
|
|
||||||
|
requires transitive envoy.common;
|
||||||
|
requires transitive java.desktop;
|
||||||
|
requires transitive java.logging;
|
||||||
|
requires transitive java.prefs;
|
||||||
|
requires javafx.controls;
|
||||||
|
requires javafx.fxml;
|
||||||
|
requires javafx.base;
|
||||||
|
requires javafx.graphics;
|
||||||
|
|
||||||
|
opens envoy.client.ui to javafx.graphics, javafx.fxml;
|
||||||
|
opens envoy.client.ui.controller to javafx.graphics, javafx.fxml;
|
||||||
|
}
|
BIN
src/main/other/CustomComponents.jar
Normal file
3
src/main/resources/client.properties
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
server=localhost
|
||||||
|
port=8080
|
||||||
|
localDB=.\\localDB
|
87
src/main/resources/css/base.css
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
.button, .list-cell, .progress-bar * {
|
||||||
|
-fx-background-radius: 5.0em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.context-menu, .context-menu > * {
|
||||||
|
-fx-background-radius: 15.0px;
|
||||||
|
/*TODO: solution below does not work */
|
||||||
|
-fx-background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-item {
|
||||||
|
-fx-background-radius: 15.0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button:hover {
|
||||||
|
-fx-scale-x: 1.05;
|
||||||
|
-fx-scale-y: 1.05;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
-fx-background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-bar:horizontal, .scroll-bar:horizontal *, .scroll-bar:horizontal > *{
|
||||||
|
-fx-background-color: transparent;
|
||||||
|
-fx-text-fill: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar{
|
||||||
|
-fx-progress-color: blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
.online {
|
||||||
|
-fx-text-fill: limegreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
.away {
|
||||||
|
-fx-text-fill: orangered;
|
||||||
|
}
|
||||||
|
|
||||||
|
.busy {
|
||||||
|
-fx-text-fill: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.offline {
|
||||||
|
-fx-text-fill: gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.received-message {
|
||||||
|
-fx-alignment: center-left;
|
||||||
|
-fx-background-radius: 4.0em;
|
||||||
|
-fx-text-alignment: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.own-message {
|
||||||
|
-fx-alignment: center-right;
|
||||||
|
-fx-background-radius: 4.0em;
|
||||||
|
-fx-text-alignment: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unreadMessagesAmount {
|
||||||
|
-fx-alignment: center;
|
||||||
|
-fx-background-color: orange;
|
||||||
|
-fx-background-radius: 4.0em;
|
||||||
|
-fx-text-alignment: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#remainingCharsLabel {
|
||||||
|
-fx-text-fill: #00FF00;
|
||||||
|
-fx-background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
#infoLabel-success {
|
||||||
|
-fx-text-fill: #00FF00;
|
||||||
|
}
|
||||||
|
|
||||||
|
#infoLabel-info {
|
||||||
|
-fx-text-fill: yellow;
|
||||||
|
}
|
||||||
|
|
||||||
|
#infoLabel-warning {
|
||||||
|
-fx-text-fill: orange;
|
||||||
|
}
|
||||||
|
|
||||||
|
#infoLabel-error {
|
||||||
|
-fx-text-fill: red;
|
||||||
|
}
|
39
src/main/resources/css/dark.css
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
* {
|
||||||
|
-fx-text-fill: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.root {
|
||||||
|
-fx-background-color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
-fx-background-color: rgb(105.0,0.0,153.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button:pressed {
|
||||||
|
-fx-background-color: darkviolet;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button:disabled {
|
||||||
|
-fx-background-color: lightgray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-view, .list-cell, .text-area .content, .text-field, .password-field, .tooltip, .pane, .pane .content, .vbox, .titled-pane > .title, .titled-pane > *.content, .context-menu, .menu-item {
|
||||||
|
-fx-background-color: dimgray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-cell:selected, .list-cell:selected > *, .menu-item:hover {
|
||||||
|
-fx-background-color: rgb(105.0,0.0,153.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.received-message {
|
||||||
|
-fx-background-color: gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.own-message {
|
||||||
|
-fx-background-color: #8fa88f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert.information.dialog-pane, .alert.warning.dialog-pane, .alert.error.dialog-pane {
|
||||||
|
-fx-background-color: black;
|
||||||
|
}
|
16
src/main/resources/css/light.css
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
.button{
|
||||||
|
-fx-background-color: orangered;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-cell:selected, .list-cell:selected > * {
|
||||||
|
-fx-background-color: orangered;
|
||||||
|
-fx-text-fill: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.received-message, .menu-item {
|
||||||
|
-fx-background-color: lightgray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.own-message {
|
||||||
|
-fx-background-color: lightgreen;
|
||||||
|
}
|
210
src/main/resources/fxml/ChatScene.fxml
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import javafx.geometry.Insets?>
|
||||||
|
<?import javafx.geometry.Rectangle2D?>
|
||||||
|
<?import javafx.scene.control.Button?>
|
||||||
|
<?import javafx.scene.control.ButtonBar?>
|
||||||
|
<?import javafx.scene.control.ContextMenu?>
|
||||||
|
<?import javafx.scene.control.Label?>
|
||||||
|
<?import javafx.scene.control.ListView?>
|
||||||
|
<?import javafx.scene.control.MenuItem?>
|
||||||
|
<?import javafx.scene.control.TextArea?>
|
||||||
|
<?import javafx.scene.control.Tooltip?>
|
||||||
|
<?import javafx.scene.image.ImageView?>
|
||||||
|
<?import javafx.scene.layout.ColumnConstraints?>
|
||||||
|
<?import javafx.scene.layout.GridPane?>
|
||||||
|
<?import javafx.scene.layout.RowConstraints?>
|
||||||
|
|
||||||
|
<GridPane fx:id="scene" hgap="5.0" maxHeight="-Infinity"
|
||||||
|
maxWidth="-Infinity" minHeight="400.0" minWidth="350.0"
|
||||||
|
prefHeight="400.0" prefWidth="600.0" vgap="2.0"
|
||||||
|
xmlns="http://javafx.com/javafx/11.0.1"
|
||||||
|
xmlns:fx="http://javafx.com/fxml/1"
|
||||||
|
fx:controller="envoy.client.ui.controller.ChatScene">
|
||||||
|
<columnConstraints>
|
||||||
|
<ColumnConstraints hgrow="NEVER" minWidth="60.0"
|
||||||
|
prefWidth="160.0" />
|
||||||
|
<ColumnConstraints hgrow="ALWAYS"
|
||||||
|
maxWidth="1.7976931348623157E308" minWidth="10.0" prefWidth="357.0" />
|
||||||
|
<ColumnConstraints hgrow="ALWAYS"
|
||||||
|
maxWidth="1.7976931348623157E308" minWidth="10.0" percentWidth="7.0"
|
||||||
|
prefWidth="357.0" />
|
||||||
|
</columnConstraints>
|
||||||
|
<rowConstraints>
|
||||||
|
<RowConstraints maxHeight="-Infinity"
|
||||||
|
minHeight="-Infinity" prefHeight="50.0" vgrow="NEVER" />
|
||||||
|
<RowConstraints maxHeight="-Infinity"
|
||||||
|
minHeight="-Infinity" prefHeight="20.0" vgrow="NEVER" />
|
||||||
|
<RowConstraints maxHeight="1.7976931348623157E308"
|
||||||
|
minHeight="50.0" prefHeight="155.14286150251115" vgrow="ALWAYS" />
|
||||||
|
<RowConstraints maxHeight="-Infinity"
|
||||||
|
minHeight="-Infinity" prefHeight="20.0" vgrow="NEVER" />
|
||||||
|
<RowConstraints maxHeight="120.0" minHeight="40.0"
|
||||||
|
prefHeight="60.0" vgrow="NEVER" />
|
||||||
|
<RowConstraints maxHeight="-Infinity"
|
||||||
|
minHeight="-Infinity" prefHeight="40.0" vgrow="NEVER" />
|
||||||
|
</rowConstraints>
|
||||||
|
<children>
|
||||||
|
<ListView fx:id="chatList" onMouseClicked="#chatListClicked"
|
||||||
|
prefHeight="211.0" prefWidth="300.0" GridPane.rowIndex="1"
|
||||||
|
GridPane.rowSpan="2147483647">
|
||||||
|
<GridPane.margin>
|
||||||
|
<Insets bottom="5.0" left="10.0" />
|
||||||
|
</GridPane.margin>
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="5.0" left="5.0" right="2.0" top="5.0" />
|
||||||
|
</padding>
|
||||||
|
<contextMenu>
|
||||||
|
<ContextMenu anchorLocation="CONTENT_TOP_LEFT">
|
||||||
|
<items>
|
||||||
|
<MenuItem fx:id="deleteContactMenuItem"
|
||||||
|
mnemonicParsing="false" onAction="#deleteContact" text="Delete" />
|
||||||
|
</items>
|
||||||
|
</ContextMenu>
|
||||||
|
</contextMenu>
|
||||||
|
</ListView>
|
||||||
|
<Label fx:id="contactLabel" prefHeight="27.0" prefWidth="134.0"
|
||||||
|
GridPane.columnSpan="2">
|
||||||
|
<GridPane.margin>
|
||||||
|
<Insets left="10.0" />
|
||||||
|
</GridPane.margin>
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||||
|
</padding>
|
||||||
|
</Label>
|
||||||
|
<Button fx:id="settingsButton" mnemonicParsing="true"
|
||||||
|
onAction="#settingsButtonClicked" text="_Settings"
|
||||||
|
GridPane.columnIndex="1" GridPane.columnSpan="2147483647"
|
||||||
|
GridPane.halignment="RIGHT" GridPane.valignment="CENTER">
|
||||||
|
<GridPane.margin>
|
||||||
|
<Insets bottom="10.0" right="10.0" top="10.0" />
|
||||||
|
</GridPane.margin>
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||||
|
</padding>
|
||||||
|
</Button>
|
||||||
|
<ListView fx:id="messageList" GridPane.columnIndex="1"
|
||||||
|
GridPane.columnSpan="2147483647" GridPane.rowIndex="1"
|
||||||
|
GridPane.rowSpan="2">
|
||||||
|
<GridPane.margin>
|
||||||
|
<Insets left="5.0" right="10.0" />
|
||||||
|
</GridPane.margin>
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="5.0" left="5.0" right="2.0" top="5.0" />
|
||||||
|
</padding>
|
||||||
|
</ListView>
|
||||||
|
<ButtonBar buttonMinWidth="40.0" GridPane.columnIndex="1"
|
||||||
|
GridPane.columnSpan="2147483647" GridPane.halignment="CENTER"
|
||||||
|
GridPane.rowIndex="5" GridPane.valignment="BOTTOM">
|
||||||
|
<GridPane.margin>
|
||||||
|
<Insets right="10.0" />
|
||||||
|
</GridPane.margin>
|
||||||
|
<buttons>
|
||||||
|
<Button fx:id="rotateButton" mnemonicParsing="false"
|
||||||
|
onAction="#doABarrelRoll">
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||||
|
</padding>
|
||||||
|
</Button>
|
||||||
|
<Button fx:id="attachmentButton" disable="true"
|
||||||
|
mnemonicParsing="false" onAction="#attachmentButtonClicked">
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||||
|
</padding>
|
||||||
|
</Button>
|
||||||
|
<Button fx:id="voiceButton" disable="true"
|
||||||
|
onAction="#voiceButtonClicked">
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||||
|
</padding>
|
||||||
|
</Button>
|
||||||
|
<Button fx:id="postButton" defaultButton="true"
|
||||||
|
disable="true" mnemonicParsing="true" onAction="#postMessage"
|
||||||
|
text="_Post">
|
||||||
|
<tooltip>
|
||||||
|
<Tooltip anchorLocation="WINDOW_TOP_LEFT" autoHide="true"
|
||||||
|
maxWidth="350.0"
|
||||||
|
text="Click this button to send the message. If it is disabled, you first have to select a contact to send it to. A message may automatically be sent when you press (Ctrl + ) Enter, according to your preferences. Additionally sends a message when pressing "Alt" + "P"."
|
||||||
|
wrapText="true" />
|
||||||
|
</tooltip>
|
||||||
|
<contextMenu>
|
||||||
|
<ContextMenu anchorLocation="CONTENT_TOP_LEFT">
|
||||||
|
<items>
|
||||||
|
<MenuItem mnemonicParsing="false"
|
||||||
|
onAction="#copyAndPostMessage" text="Copy and Send" />
|
||||||
|
</items>
|
||||||
|
</ContextMenu>
|
||||||
|
</contextMenu>
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||||
|
</padding>
|
||||||
|
</Button>
|
||||||
|
</buttons>
|
||||||
|
</ButtonBar>
|
||||||
|
<TextArea fx:id="messageTextArea" disable="true"
|
||||||
|
onInputMethodTextChanged="#messageTextUpdated"
|
||||||
|
onKeyPressed="#checkPostConditions" onKeyTyped="#checkKeyCombination"
|
||||||
|
prefHeight="200.0" prefWidth="200.0" wrapText="true"
|
||||||
|
GridPane.columnIndex="1" GridPane.columnSpan="2147483647"
|
||||||
|
GridPane.rowIndex="4">
|
||||||
|
<GridPane.margin>
|
||||||
|
<Insets bottom="10.0" left="5.0" right="10.0" top="3.0" />
|
||||||
|
</GridPane.margin>
|
||||||
|
<opaqueInsets>
|
||||||
|
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||||
|
</opaqueInsets>
|
||||||
|
</TextArea>
|
||||||
|
<Button mnemonicParsing="true"
|
||||||
|
onAction="#addContactButtonClicked" text="_Add Contacts"
|
||||||
|
GridPane.halignment="CENTER" GridPane.rowIndex="5"
|
||||||
|
GridPane.valignment="CENTER">
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||||
|
</padding>
|
||||||
|
<GridPane.margin>
|
||||||
|
<Insets bottom="10.0" left="10.0" right="5.0" />
|
||||||
|
</GridPane.margin>
|
||||||
|
</Button>
|
||||||
|
<Label id="remainingCharsLabel" fx:id="remainingChars"
|
||||||
|
ellipsisString="" maxHeight="30.0" maxWidth="180.0" prefHeight="30.0"
|
||||||
|
prefWidth="180.0" text="remaining chars: 0/x" textFill="LIME"
|
||||||
|
textOverrun="LEADING_WORD_ELLIPSIS" visible="false"
|
||||||
|
GridPane.columnIndex="1" GridPane.rowIndex="3">
|
||||||
|
<GridPane.margin>
|
||||||
|
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||||
|
</GridPane.margin>
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="5.0" top="5.0" />
|
||||||
|
</padding>
|
||||||
|
<opaqueInsets>
|
||||||
|
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||||
|
</opaqueInsets>
|
||||||
|
<tooltip>
|
||||||
|
<Tooltip
|
||||||
|
text="Shows how many chars you can still enter in this message"
|
||||||
|
wrapText="true" />
|
||||||
|
</tooltip>
|
||||||
|
</Label>
|
||||||
|
<Label fx:id="infoLabel" text="Something happened"
|
||||||
|
textFill="#faa007" visible="false" wrapText="true"
|
||||||
|
GridPane.columnIndex="1">
|
||||||
|
<GridPane.margin>
|
||||||
|
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||||
|
</GridPane.margin>
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||||
|
</padding>
|
||||||
|
</Label>
|
||||||
|
<ImageView fx:id="attachmentView" pickOnBounds="true"
|
||||||
|
preserveRatio="true" visible="false" GridPane.columnIndex="1"
|
||||||
|
GridPane.columnSpan="2147483647" GridPane.halignment="RIGHT"
|
||||||
|
GridPane.rowIndex="3">
|
||||||
|
<viewport>
|
||||||
|
<Rectangle2D height="20.0" width="20.0" />
|
||||||
|
</viewport>
|
||||||
|
<GridPane.margin>
|
||||||
|
<Insets bottom="5.0" right="10.0" top="5.0" />
|
||||||
|
</GridPane.margin>
|
||||||
|
</ImageView>
|
||||||
|
</children>
|
||||||
|
</GridPane>
|
74
src/main/resources/fxml/ContactSearchScene.fxml
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import envoy.client.ui.ClearableTextField?>
|
||||||
|
<?import javafx.geometry.Insets?>
|
||||||
|
<?import javafx.scene.control.Button?>
|
||||||
|
<?import javafx.scene.control.ListView?>
|
||||||
|
<?import javafx.scene.control.Tooltip?>
|
||||||
|
<?import javafx.scene.layout.HBox?>
|
||||||
|
<?import javafx.scene.layout.VBox?>
|
||||||
|
|
||||||
|
<VBox maxHeight="-Infinity" maxWidth="-Infinity"
|
||||||
|
minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0"
|
||||||
|
prefWidth="600.0" xmlns="http://javafx.com/javafx/11.0.1"
|
||||||
|
xmlns:fx="http://javafx.com/fxml/1"
|
||||||
|
fx:controller="envoy.client.ui.controller.ContactSearchScene">
|
||||||
|
<children>
|
||||||
|
<HBox alignment="CENTER" prefHeight="100.0" prefWidth="200.0">
|
||||||
|
<children>
|
||||||
|
<ClearableTextField fx:id="searchBar"
|
||||||
|
prefWidth="310.0">
|
||||||
|
<textField onInputMethodTextChanged="#sendRequest"
|
||||||
|
onKeyTyped="#sendRequest" prefColumnCount="22"
|
||||||
|
promptText="Enter username to search for">
|
||||||
|
</textField>
|
||||||
|
<HBox.margin>
|
||||||
|
<Insets bottom="5.0" left="5.0" right="5.0" top="15.0" />
|
||||||
|
</HBox.margin>
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||||
|
</padding>
|
||||||
|
<tooltip>
|
||||||
|
<Tooltip
|
||||||
|
text="Enter a name. If an account by that name exists, it will be displayed below."
|
||||||
|
wrapText="true" />
|
||||||
|
</tooltip>
|
||||||
|
</ClearableTextField>
|
||||||
|
<Button mnemonicParsing="false"
|
||||||
|
onAction="#newGroupButtonClicked" prefHeight="26.0"
|
||||||
|
prefWidth="139.0" text="New Group">
|
||||||
|
<HBox.margin>
|
||||||
|
<Insets bottom="5.0" left="30.0" right="5.0" top="5.0" />
|
||||||
|
</HBox.margin>
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||||
|
</padding>
|
||||||
|
</Button>
|
||||||
|
</children>
|
||||||
|
</HBox>
|
||||||
|
<ListView fx:id="chatList"
|
||||||
|
onMouseClicked="#chatListClicked" prefHeight="314.0"
|
||||||
|
prefWidth="600.0">
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||||
|
</padding>
|
||||||
|
<VBox.margin>
|
||||||
|
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
|
||||||
|
</VBox.margin>
|
||||||
|
</ListView>
|
||||||
|
<Button cancelButton="true" mnemonicParsing="true"
|
||||||
|
onAction="#backButtonClicked" text="_Back">
|
||||||
|
<VBox.margin>
|
||||||
|
<Insets bottom="10.0" left="10.0" />
|
||||||
|
</VBox.margin>
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||||
|
</padding>
|
||||||
|
<tooltip>
|
||||||
|
<Tooltip autoHide="true"
|
||||||
|
text="Takes you back to the screen where you can chat with others"
|
||||||
|
wrapText="true" />
|
||||||
|
</tooltip>
|
||||||
|
</Button>
|
||||||
|
</children>
|
||||||
|
</VBox>
|
91
src/main/resources/fxml/GroupCreationScene.fxml
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import envoy.client.ui.ClearableTextField?>
|
||||||
|
<?import javafx.geometry.Insets?>
|
||||||
|
<?import javafx.scene.control.Button?>
|
||||||
|
<?import javafx.scene.control.Label?>
|
||||||
|
<?import javafx.scene.control.ListView?>
|
||||||
|
<?import javafx.scene.control.Tooltip?>
|
||||||
|
<?import javafx.scene.layout.BorderPane?>
|
||||||
|
<?import javafx.scene.layout.HBox?>
|
||||||
|
<?import javafx.scene.layout.VBox?>
|
||||||
|
<?import javafx.scene.text.Font?>
|
||||||
|
|
||||||
|
<VBox maxHeight="-Infinity" maxWidth="-Infinity"
|
||||||
|
minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0"
|
||||||
|
prefWidth="600.0" xmlns="http://javafx.com/javafx/11.0.1"
|
||||||
|
xmlns:fx="http://javafx.com/fxml/1"
|
||||||
|
fx:controller="envoy.client.ui.controller.GroupCreationScene">
|
||||||
|
<children>
|
||||||
|
<HBox alignment="CENTER" prefHeight="100.0" prefWidth="200.0">
|
||||||
|
<children>
|
||||||
|
<ClearableTextField fx:id="groupNameField">
|
||||||
|
<textField prefColumnCount="22"
|
||||||
|
promptText="Enter Group Name"
|
||||||
|
onInputMethodTextChanged="#textUpdated" onKeyTyped="#textUpdated" />
|
||||||
|
<HBox.margin>
|
||||||
|
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||||
|
</HBox.margin>
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||||
|
</padding>
|
||||||
|
<tooltip>
|
||||||
|
<Tooltip
|
||||||
|
text="Enter something. A group with this name will be created."
|
||||||
|
wrapText="true" />
|
||||||
|
</tooltip>
|
||||||
|
</ClearableTextField>
|
||||||
|
</children>
|
||||||
|
</HBox>
|
||||||
|
<Label text="Choose Members:">
|
||||||
|
<font>
|
||||||
|
<Font size="16.0" />
|
||||||
|
</font>
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||||
|
</padding>
|
||||||
|
</Label>
|
||||||
|
<ListView fx:id="chatList"
|
||||||
|
onMouseClicked="#chatListClicked" prefHeight="314.0"
|
||||||
|
prefWidth="600.0">
|
||||||
|
<VBox.margin>
|
||||||
|
<Insets bottom="5.0" left="10.0" right="10.0" top="5.0" />
|
||||||
|
</VBox.margin>
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||||
|
</padding>
|
||||||
|
</ListView>
|
||||||
|
<BorderPane prefHeight="50.0">
|
||||||
|
<left>
|
||||||
|
<Button cancelButton="true" mnemonicParsing="true"
|
||||||
|
onAction="#backButtonClicked" text="_Back"
|
||||||
|
BorderPane.alignment="CENTER">
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||||
|
</padding>
|
||||||
|
<tooltip>
|
||||||
|
<Tooltip autoHide="true"
|
||||||
|
text="Takes you back to the screen where you can chat with others"
|
||||||
|
wrapText="true" />
|
||||||
|
</tooltip>
|
||||||
|
<BorderPane.margin>
|
||||||
|
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
|
||||||
|
</BorderPane.margin>
|
||||||
|
</Button>
|
||||||
|
</left>
|
||||||
|
<right>
|
||||||
|
<Button fx:id="createButton" alignment="CENTER_RIGHT"
|
||||||
|
defaultButton="true" disable="true" mnemonicParsing="false"
|
||||||
|
onAction="#createButtonClicked" text="Create"
|
||||||
|
BorderPane.alignment="CENTER">
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||||
|
</padding>
|
||||||
|
<BorderPane.margin>
|
||||||
|
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
|
||||||
|
</BorderPane.margin>
|
||||||
|
</Button>
|
||||||
|
</right>
|
||||||
|
</BorderPane>
|
||||||
|
</children>
|
||||||
|
</VBox>
|
149
src/main/resources/fxml/LoginScene.fxml
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import envoy.client.ui.ClearableTextField?>
|
||||||
|
<?import javafx.geometry.Insets?>
|
||||||
|
<?import javafx.scene.control.Button?>
|
||||||
|
<?import javafx.scene.control.CheckBox?>
|
||||||
|
<?import javafx.scene.control.Label?>
|
||||||
|
<?import javafx.scene.control.PasswordField?>
|
||||||
|
<?import javafx.scene.layout.BorderPane?>
|
||||||
|
<?import javafx.scene.layout.ColumnConstraints?>
|
||||||
|
<?import javafx.scene.layout.GridPane?>
|
||||||
|
<?import javafx.scene.layout.RowConstraints?>
|
||||||
|
<?import javafx.scene.layout.VBox?>
|
||||||
|
<?import javafx.scene.text.Font?>
|
||||||
|
|
||||||
|
<VBox prefHeight="206.0" prefWidth="440.0"
|
||||||
|
xmlns="http://javafx.com/javafx/11.0.1"
|
||||||
|
xmlns:fx="http://javafx.com/fxml/1"
|
||||||
|
fx:controller="envoy.client.ui.controller.LoginScene">
|
||||||
|
<children>
|
||||||
|
<Label text="User Login">
|
||||||
|
<font>
|
||||||
|
<Font size="26.0" />
|
||||||
|
</font>
|
||||||
|
<VBox.margin>
|
||||||
|
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||||
|
</VBox.margin>
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||||
|
</padding>
|
||||||
|
</Label>
|
||||||
|
<GridPane hgap="5.0" vgap="10.0">
|
||||||
|
<columnConstraints>
|
||||||
|
<ColumnConstraints hgrow="SOMETIMES"
|
||||||
|
minWidth="10.0" percentWidth="40.0" prefWidth="100.0" />
|
||||||
|
<ColumnConstraints hgrow="SOMETIMES"
|
||||||
|
minWidth="10.0" prefWidth="100.0" />
|
||||||
|
</columnConstraints>
|
||||||
|
<rowConstraints>
|
||||||
|
<RowConstraints minHeight="10.0" prefHeight="30.0"
|
||||||
|
vgrow="SOMETIMES" />
|
||||||
|
<RowConstraints minHeight="10.0" prefHeight="30.0"
|
||||||
|
vgrow="SOMETIMES" />
|
||||||
|
<RowConstraints minHeight="10.0" prefHeight="30.0"
|
||||||
|
vgrow="SOMETIMES" />
|
||||||
|
</rowConstraints>
|
||||||
|
<children>
|
||||||
|
<Label text="User Name:">
|
||||||
|
<GridPane.margin>
|
||||||
|
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||||
|
</GridPane.margin>
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="2.0" left="2.0" right="2.0" top="2.0" />
|
||||||
|
</padding>
|
||||||
|
</Label>
|
||||||
|
<Label text="Password:" GridPane.rowIndex="1">
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="2.0" left="2.0" right="2.0" top="2.0" />
|
||||||
|
</padding>
|
||||||
|
<GridPane.margin>
|
||||||
|
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||||
|
</GridPane.margin>
|
||||||
|
</Label>
|
||||||
|
<Label fx:id="repeatPasswordLabel" text="Repeat Password:"
|
||||||
|
visible="false" GridPane.rowIndex="2">
|
||||||
|
<GridPane.margin>
|
||||||
|
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||||
|
</GridPane.margin>
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="2.0" left="2.0" right="2.0" top="2.0" />
|
||||||
|
</padding>
|
||||||
|
</Label>
|
||||||
|
<ClearableTextField fx:id="userTextField"
|
||||||
|
GridPane.columnIndex="1">
|
||||||
|
<GridPane.margin>
|
||||||
|
<Insets bottom="10.0" left="5.0" right="5.0" top="5.0" />
|
||||||
|
</GridPane.margin>
|
||||||
|
</ClearableTextField>
|
||||||
|
<PasswordField fx:id="passwordField"
|
||||||
|
GridPane.columnIndex="1" GridPane.rowIndex="1">
|
||||||
|
<GridPane.margin>
|
||||||
|
<Insets bottom="10.0" left="5.0" right="5.0" top="10.0" />
|
||||||
|
</GridPane.margin>
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||||
|
</padding>
|
||||||
|
</PasswordField>
|
||||||
|
<PasswordField fx:id="repeatPasswordField"
|
||||||
|
visible="false" GridPane.columnIndex="1" GridPane.rowIndex="2">
|
||||||
|
<GridPane.margin>
|
||||||
|
<Insets bottom="5.0" left="5.0" right="5.0" top="10.0" />
|
||||||
|
</GridPane.margin>
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||||
|
</padding>
|
||||||
|
</PasswordField>
|
||||||
|
</children>
|
||||||
|
<VBox.margin>
|
||||||
|
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||||
|
</VBox.margin>
|
||||||
|
</GridPane>
|
||||||
|
<CheckBox fx:id="registerCheckBox" mnemonicParsing="true"
|
||||||
|
onAction="#registerCheckboxChanged" prefHeight="17.0"
|
||||||
|
prefWidth="181.0" text="_Register">
|
||||||
|
<VBox.margin>
|
||||||
|
<Insets left="5.0" right="3.0" />
|
||||||
|
</VBox.margin>
|
||||||
|
</CheckBox>
|
||||||
|
<Label fx:id="connectionLabel">
|
||||||
|
<VBox.margin>
|
||||||
|
<Insets left="5.0" />
|
||||||
|
</VBox.margin>
|
||||||
|
</Label>
|
||||||
|
<BorderPane prefWidth="200.0">
|
||||||
|
<left>
|
||||||
|
<Button cancelButton="true" mnemonicParsing="false"
|
||||||
|
onAction="#abortLogin" text="Close" BorderPane.alignment="CENTER">
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||||
|
</padding>
|
||||||
|
<BorderPane.margin>
|
||||||
|
<Insets />
|
||||||
|
</BorderPane.margin>
|
||||||
|
</Button>
|
||||||
|
</left>
|
||||||
|
<center>
|
||||||
|
<Button mnemonicParsing="false"
|
||||||
|
onAction="#offlineModeButtonPressed" text="Offline mode"
|
||||||
|
BorderPane.alignment="CENTER">
|
||||||
|
<BorderPane.margin>
|
||||||
|
<Insets />
|
||||||
|
</BorderPane.margin>
|
||||||
|
</Button>
|
||||||
|
</center>
|
||||||
|
<right>
|
||||||
|
<Button defaultButton="true" mnemonicParsing="false"
|
||||||
|
onAction="#loginButtonPressed" text="Login"
|
||||||
|
BorderPane.alignment="CENTER">
|
||||||
|
<BorderPane.margin>
|
||||||
|
<Insets />
|
||||||
|
</BorderPane.margin>
|
||||||
|
</Button>
|
||||||
|
</right>
|
||||||
|
<VBox.margin>
|
||||||
|
<Insets bottom="5.0" left="5.0" right="5.0" />
|
||||||
|
</VBox.margin>
|
||||||
|
</BorderPane>
|
||||||
|
</children>
|
||||||
|
</VBox>
|
56
src/main/resources/fxml/SettingsScene.fxml
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import javafx.geometry.Insets?>
|
||||||
|
<?import javafx.scene.control.Button?>
|
||||||
|
<?import javafx.scene.control.ListView?>
|
||||||
|
<?import javafx.scene.control.TitledPane?>
|
||||||
|
<?import javafx.scene.layout.HBox?>
|
||||||
|
<?import javafx.scene.layout.VBox?>
|
||||||
|
|
||||||
|
<VBox alignment="TOP_RIGHT" maxHeight="-Infinity"
|
||||||
|
maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity"
|
||||||
|
prefHeight="400.0" prefWidth="600.0"
|
||||||
|
xmlns="http://javafx.com/javafx/11.0.1"
|
||||||
|
xmlns:fx="http://javafx.com/fxml/1"
|
||||||
|
fx:controller="envoy.client.ui.controller.SettingsScene">
|
||||||
|
<children>
|
||||||
|
<HBox prefHeight="389.0" prefWidth="600.0">
|
||||||
|
<children>
|
||||||
|
<ListView fx:id="settingsList"
|
||||||
|
onMouseClicked="#settingsListClicked" prefHeight="200.0"
|
||||||
|
prefWidth="200.0">
|
||||||
|
<opaqueInsets>
|
||||||
|
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||||
|
</opaqueInsets>
|
||||||
|
<HBox.margin>
|
||||||
|
<Insets bottom="10.0" left="10.0" right="5.0" top="10.0" />
|
||||||
|
</HBox.margin>
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||||
|
</padding>
|
||||||
|
</ListView>
|
||||||
|
<TitledPane fx:id="titledPane" collapsible="false"
|
||||||
|
prefHeight="325.0" prefWidth="300.0">
|
||||||
|
<HBox.margin>
|
||||||
|
<Insets bottom="10.0" left="5.0" right="10.0" top="10.0" />
|
||||||
|
</HBox.margin>
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||||
|
</padding>
|
||||||
|
</TitledPane>
|
||||||
|
</children>
|
||||||
|
</HBox>
|
||||||
|
<Button defaultButton="true" mnemonicParsing="true"
|
||||||
|
onMouseClicked="#backButtonClicked" text="_Back">
|
||||||
|
<opaqueInsets>
|
||||||
|
<Insets />
|
||||||
|
</opaqueInsets>
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||||
|
</padding>
|
||||||
|
<VBox.margin>
|
||||||
|
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||||
|
</VBox.margin>
|
||||||
|
</Button>
|
||||||
|
</children>
|
||||||
|
</VBox>
|
BIN
src/main/resources/icons/dark/attachment.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
src/main/resources/icons/dark/attachment_present.png
Normal file
After Width: | Height: | Size: 31 KiB |
BIN
src/main/resources/icons/dark/clear_button.png
Normal file
After Width: | Height: | Size: 3.8 KiB |
BIN
src/main/resources/icons/dark/forward.png
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
src/main/resources/icons/dark/microphone.png
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
src/main/resources/icons/dark/rotate.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
src/main/resources/icons/dark/search.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
src/main/resources/icons/dark/settings.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
src/main/resources/icons/envoy_logo.png
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
src/main/resources/icons/envoy_logo_alpha.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
src/main/resources/icons/light/attachment.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
src/main/resources/icons/light/attachment_present.png
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
src/main/resources/icons/light/clear_button.png
Normal file
After Width: | Height: | Size: 4.2 KiB |
BIN
src/main/resources/icons/light/forward.png
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
src/main/resources/icons/light/microphone.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
src/main/resources/icons/light/rotate.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
src/main/resources/icons/light/search.png
Normal file
After Width: | Height: | Size: 8.3 KiB |
BIN
src/main/resources/icons/light/settings.png
Normal file
After Width: | Height: | Size: 3.4 KiB |