├── .classpath ├── .gitignore ├── .project ├── .settings ├── net.sf.jautodoc.prefs ├── org.eclipse.jdt.core.prefs └── org.eclipse.jdt.ui.prefs ├── NOTICE ├── README.md ├── bnd.bnd ├── build.xml ├── src └── nl │ └── lxtreme │ └── jvt220 │ └── terminal │ ├── ICursor.java │ ├── ITabulator.java │ ├── ITerminal.java │ ├── ITerminalColorScheme.java │ ├── ITerminalFrontend.java │ ├── packageinfo │ ├── swing │ ├── CharBuffer.java │ ├── SwingFrontend.java │ ├── XtermColorScheme.java │ └── packageinfo │ └── vt220 │ ├── AbstractTerminal.java │ ├── CharacterSets.java │ ├── CursorImpl.java │ ├── PlainTerminal.java │ ├── TextAttributes.java │ ├── TextCell.java │ ├── VT220Parser.java │ ├── VT220Terminal.java │ └── packageinfo └── test └── nl └── lxtreme └── jvt220 └── terminal ├── swing └── CharBufferTest.java └── vt220 ├── DefaultTabulatorTest.java ├── PlainTerminalTest.java ├── VT220ParserTest.java └── VT220TerminalTest.java /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | bin_test 3 | generated 4 | 5 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | jvt220 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | bndtools.core.bndbuilder 15 | 16 | 17 | 18 | 19 | 20 | org.eclipse.jdt.core.javanature 21 | bndtools.core.bndnature 22 | 23 | 24 | -------------------------------------------------------------------------------- /.settings/net.sf.jautodoc.prefs: -------------------------------------------------------------------------------- 1 | add_header=true 2 | eclipse.preferences.version=1 3 | header_text=/**\n * jVT220 - Java VT220 terminal emulator.\n * \n * Licensed to the Apache Software Foundation (ASF) under one\n * or more contributor license agreements. See the NOTICE file\n * distributed with this work for additional information\n * regarding copyright ownership. The ASF licenses this file\n * to you under the Apache License, Version 2.0 (the\n * "License"); you may not use this file except in compliance\n * with the License. You may obtain a copy of the License at\n *\n * http\://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing,\n * software distributed under the License is distributed on an\n * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n * KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations\n * under the License.\n */ 4 | include_subpackages=true 5 | project_specific_settings=true 6 | replace_header=true 7 | replacements=\n\n\nGets the\nSets the\nAdds the\nEdits the\nRemoves the\nInits the\nParses the\nCreates the\nBuilds the\nChecks if is\nPrints the\nChecks for\n\n\n 8 | -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.codeComplete.argumentPrefixes= 3 | org.eclipse.jdt.core.codeComplete.argumentSuffixes= 4 | org.eclipse.jdt.core.codeComplete.fieldPrefixes=m_ 5 | org.eclipse.jdt.core.codeComplete.fieldSuffixes= 6 | org.eclipse.jdt.core.codeComplete.localPrefixes= 7 | org.eclipse.jdt.core.codeComplete.localSuffixes= 8 | org.eclipse.jdt.core.codeComplete.staticFieldPrefixes= 9 | org.eclipse.jdt.core.codeComplete.staticFieldSuffixes= 10 | org.eclipse.jdt.core.codeComplete.staticFinalFieldPrefixes= 11 | org.eclipse.jdt.core.codeComplete.staticFinalFieldSuffixes= 12 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled 13 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 14 | org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve 15 | org.eclipse.jdt.core.compiler.compliance=1.6 16 | org.eclipse.jdt.core.compiler.debug.lineNumber=generate 17 | org.eclipse.jdt.core.compiler.debug.localVariable=generate 18 | org.eclipse.jdt.core.compiler.debug.sourceFile=generate 19 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error 20 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error 21 | org.eclipse.jdt.core.compiler.source=1.6 22 | org.eclipse.jdt.core.formatter.align_type_members_on_columns=false 23 | org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16 24 | org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0 25 | org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16 26 | org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16 27 | org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16 28 | org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16 29 | org.eclipse.jdt.core.formatter.alignment_for_assignment=0 30 | org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16 31 | org.eclipse.jdt.core.formatter.alignment_for_compact_if=16 32 | org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80 33 | org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0 34 | org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16 35 | org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0 36 | org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16 37 | org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16 38 | org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16 39 | org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=80 40 | org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16 41 | org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16 42 | org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16 43 | org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16 44 | org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16 45 | org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16 46 | org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16 47 | org.eclipse.jdt.core.formatter.blank_lines_after_imports=2 48 | org.eclipse.jdt.core.formatter.blank_lines_after_package=2 49 | org.eclipse.jdt.core.formatter.blank_lines_before_field=0 50 | org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0 51 | org.eclipse.jdt.core.formatter.blank_lines_before_imports=1 52 | org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1 53 | org.eclipse.jdt.core.formatter.blank_lines_before_method=1 54 | org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1 55 | org.eclipse.jdt.core.formatter.blank_lines_before_package=0 56 | org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1 57 | org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=2 58 | org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=next_line 59 | org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=next_line 60 | org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line 61 | org.eclipse.jdt.core.formatter.brace_position_for_block=next_line 62 | org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=next_line 63 | org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=next_line 64 | org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=next_line 65 | org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=next_line 66 | org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=next_line 67 | org.eclipse.jdt.core.formatter.brace_position_for_switch=next_line 68 | org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=next_line 69 | org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=true 70 | org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=true 71 | org.eclipse.jdt.core.formatter.comment.format_block_comments=true 72 | org.eclipse.jdt.core.formatter.comment.format_header=false 73 | org.eclipse.jdt.core.formatter.comment.format_html=true 74 | org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true 75 | org.eclipse.jdt.core.formatter.comment.format_line_comments=true 76 | org.eclipse.jdt.core.formatter.comment.format_source_code=true 77 | org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true 78 | org.eclipse.jdt.core.formatter.comment.indent_root_tags=true 79 | org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert 80 | org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=insert 81 | org.eclipse.jdt.core.formatter.comment.line_length=80 82 | org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true 83 | org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true 84 | org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false 85 | org.eclipse.jdt.core.formatter.compact_else_if=true 86 | org.eclipse.jdt.core.formatter.continuation_indentation=2 87 | org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2 88 | org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off 89 | org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on 90 | org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false 91 | org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true 92 | org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true 93 | org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true 94 | org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true 95 | org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true 96 | org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true 97 | org.eclipse.jdt.core.formatter.indent_empty_lines=false 98 | org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true 99 | org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true 100 | org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true 101 | org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=true 102 | org.eclipse.jdt.core.formatter.indentation.size=4 103 | org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert 104 | org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=do not insert 105 | org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert 106 | org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert 107 | org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert 108 | org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert 109 | org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert 110 | org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert 111 | org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=insert 112 | org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=insert 113 | org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert 114 | org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=insert 115 | org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=insert 116 | org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=insert 117 | org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert 118 | org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=insert 119 | org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert 120 | org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=insert 121 | org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert 122 | org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert 123 | org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert 124 | org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert 125 | org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert 126 | org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert 127 | org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert 128 | org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert 129 | org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert 130 | org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert 131 | org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert 132 | org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=do not insert 133 | org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert 134 | org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert 135 | org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert 136 | org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert 137 | org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert 138 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert 139 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert 140 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert 141 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert 142 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert 143 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert 144 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert 145 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert 146 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert 147 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert 148 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert 149 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert 150 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert 151 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert 152 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert 153 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert 154 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert 155 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert 156 | org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert 157 | org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert 158 | org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert 159 | org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert 160 | org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert 161 | org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert 162 | org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert 163 | org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert 164 | org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=insert 165 | org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=insert 166 | org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=insert 167 | org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=insert 168 | org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=insert 169 | org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=insert 170 | org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=insert 171 | org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=insert 172 | org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=insert 173 | org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=insert 174 | org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=insert 175 | org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=insert 176 | org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try=do not insert 177 | org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=insert 178 | org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert 179 | org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert 180 | org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert 181 | org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert 182 | org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert 183 | org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert 184 | org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert 185 | org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert 186 | org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert 187 | org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert 188 | org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert 189 | org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert 190 | org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert 191 | org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert 192 | org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert 193 | org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert 194 | org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert 195 | org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=insert 196 | org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=insert 197 | org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=insert 198 | org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=insert 199 | org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=insert 200 | org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=insert 201 | org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=insert 202 | org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=insert 203 | org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=insert 204 | org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=insert 205 | org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=insert 206 | org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=insert 207 | org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try=do not insert 208 | org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=insert 209 | org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert 210 | org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert 211 | org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert 212 | org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert 213 | org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert 214 | org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert 215 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert 216 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert 217 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert 218 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert 219 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert 220 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert 221 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert 222 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert 223 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert 224 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert 225 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert 226 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert 227 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert 228 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert 229 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert 230 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert 231 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert 232 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert 233 | org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert 234 | org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert 235 | org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert 236 | org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert 237 | org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert 238 | org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert 239 | org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert 240 | org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert 241 | org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert 242 | org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert 243 | org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert 244 | org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert 245 | org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert 246 | org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert 247 | org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert 248 | org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert 249 | org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert 250 | org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert 251 | org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert 252 | org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert 253 | org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert 254 | org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert 255 | org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert 256 | org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert 257 | org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert 258 | org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert 259 | org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert 260 | org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert 261 | org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert 262 | org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert 263 | org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try=insert 264 | org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert 265 | org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert 266 | org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert 267 | org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert 268 | org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert 269 | org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert 270 | org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert 271 | org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert 272 | org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert 273 | org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert 274 | org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert 275 | org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert 276 | org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert 277 | org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert 278 | org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert 279 | org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert 280 | org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert 281 | org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert 282 | org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert 283 | org.eclipse.jdt.core.formatter.join_lines_in_comments=true 284 | org.eclipse.jdt.core.formatter.join_wrapped_lines=true 285 | org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false 286 | org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=true 287 | org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false 288 | org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false 289 | org.eclipse.jdt.core.formatter.lineSplit=120 290 | org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false 291 | org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false 292 | org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0 293 | org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1 294 | org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true 295 | org.eclipse.jdt.core.formatter.tabulation.char=space 296 | org.eclipse.jdt.core.formatter.tabulation.size=2 297 | org.eclipse.jdt.core.formatter.use_on_off_tags=true 298 | org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false 299 | org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true 300 | org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true 301 | org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true 302 | -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.ui.prefs: -------------------------------------------------------------------------------- 1 | cleanup.add_default_serial_version_id=true 2 | cleanup.add_generated_serial_version_id=false 3 | cleanup.add_missing_annotations=true 4 | cleanup.add_missing_deprecated_annotations=true 5 | cleanup.add_missing_methods=true 6 | cleanup.add_missing_nls_tags=false 7 | cleanup.add_missing_override_annotations=true 8 | cleanup.add_missing_override_annotations_interface_methods=true 9 | cleanup.add_serial_version_id=false 10 | cleanup.always_use_blocks=true 11 | cleanup.always_use_parentheses_in_expressions=true 12 | cleanup.always_use_this_for_non_static_field_access=false 13 | cleanup.always_use_this_for_non_static_method_access=false 14 | cleanup.convert_to_enhanced_for_loop=false 15 | cleanup.correct_indentation=true 16 | cleanup.format_source_code=true 17 | cleanup.format_source_code_changes_only=false 18 | cleanup.make_local_variable_final=false 19 | cleanup.make_parameters_final=false 20 | cleanup.make_private_fields_final=true 21 | cleanup.make_type_abstract_if_missing_method=false 22 | cleanup.make_variable_declarations_final=true 23 | cleanup.never_use_blocks=false 24 | cleanup.never_use_parentheses_in_expressions=false 25 | cleanup.organize_imports=true 26 | cleanup.qualify_static_field_accesses_with_declaring_class=false 27 | cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true 28 | cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true 29 | cleanup.qualify_static_member_accesses_with_declaring_class=true 30 | cleanup.qualify_static_method_accesses_with_declaring_class=false 31 | cleanup.remove_private_constructors=true 32 | cleanup.remove_trailing_whitespaces=true 33 | cleanup.remove_trailing_whitespaces_all=true 34 | cleanup.remove_trailing_whitespaces_ignore_empty=false 35 | cleanup.remove_unnecessary_casts=true 36 | cleanup.remove_unnecessary_nls_tags=true 37 | cleanup.remove_unused_imports=true 38 | cleanup.remove_unused_local_variables=true 39 | cleanup.remove_unused_private_fields=true 40 | cleanup.remove_unused_private_members=false 41 | cleanup.remove_unused_private_methods=true 42 | cleanup.remove_unused_private_types=true 43 | cleanup.sort_members=true 44 | cleanup.sort_members_all=false 45 | cleanup.use_blocks=true 46 | cleanup.use_blocks_only_for_return_and_throw=false 47 | cleanup.use_parentheses_in_expressions=true 48 | cleanup.use_this_for_non_static_field_access=true 49 | cleanup.use_this_for_non_static_field_access_only_if_necessary=true 50 | cleanup.use_this_for_non_static_method_access=false 51 | cleanup.use_this_for_non_static_method_access_only_if_necessary=true 52 | cleanup_profile=_L'Xtreme IT consultancy 53 | cleanup_settings_version=2 54 | eclipse.preferences.version=1 55 | formatter_profile=_L'Xtreme code conventions 56 | formatter_settings_version=12 57 | org.eclipse.jdt.ui.exception.name=e 58 | org.eclipse.jdt.ui.gettersetter.use.is=true 59 | org.eclipse.jdt.ui.ignorelowercasenames=true 60 | org.eclipse.jdt.ui.importorder=java;javax;org;com; 61 | org.eclipse.jdt.ui.javadoc=true 62 | org.eclipse.jdt.ui.keywordthis=false 63 | org.eclipse.jdt.ui.ondemandthreshold=1 64 | org.eclipse.jdt.ui.overrideannotation=true 65 | org.eclipse.jdt.ui.staticondemandthreshold=1 66 | org.eclipse.jdt.ui.text.custom_code_templates= 67 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | jVT220 - Java VT220 terminal emulator. 2 | 3 | (C) Copyright 2012 - J.W. Janssen . 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jVT220 - Java VT220 terminal emulator 2 | 3 | This library provides a small VT220 terminal emulator written in Java. It 4 | attempts to emulate a VT220 as closely as possible and implements some of 5 | the functionality found in other types of terminals or terminal emulators, 6 | like VT340 and XTerm. 7 | 8 | ## History 9 | 10 | ``` 11 | 2013-01-12 | JaWi | v1.0.0 | Initial release. 12 | 2013-02-05 | JaWi | v1.1.0 | Fixed incorrect calculation of heat map when scrolling up; improved the repainting of the Swing frontend; allow character encoding to be specified; allow external writes to the terminal. 13 | 2013-02-05 | JaWi | v1.1.1 | The terminal calls the Swing frontend not from the EDT, ensure that all Swing-related actions are called from the EDT. 14 | ``` 15 | 16 | ## Author 17 | 18 | This library is written by J.W. Janssen, . 19 | 20 | ## License 21 | 22 | The code in this library is licensed under Apache Software License, version 23 | 2.0 and can be found online at: . 24 | 25 | -------------------------------------------------------------------------------- /bnd.bnd: -------------------------------------------------------------------------------- 1 | Bundle-Version: 1.1.1 2 | Export-Package: \ 3 | nl.lxtreme.jvt220.terminal,\ 4 | nl.lxtreme.jvt220.terminal.vt220,\ 5 | nl.lxtreme.jvt220.terminal.swing 6 | -buildpath: junit.osgi;version=3.8.2 -------------------------------------------------------------------------------- /build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/nl/lxtreme/jvt220/terminal/ICursor.java: -------------------------------------------------------------------------------- 1 | /** 2 | * jVT220 - Java VT220 terminal emulator. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | package nl.lxtreme.jvt220.terminal; 22 | 23 | 24 | /** 25 | * Denotes a cursor, or the current write-position of a terminal. 26 | */ 27 | public interface ICursor extends Cloneable 28 | { 29 | // METHODS 30 | 31 | /** 32 | * Returns a clone of this cursor instance. 33 | * 34 | * @return a clone of this cursor, never null. 35 | * @see Cloneable 36 | */ 37 | ICursor clone(); 38 | 39 | /** 40 | * Returns the blinking rate of this cursor. 41 | * 42 | * @return the current blinking rate, in milliseconds. 43 | * @see #setBlinkRate(int) 44 | */ 45 | int getBlinkRate(); 46 | 47 | /** 48 | * Returns the X-position of the cursor. 49 | * 50 | * @return a X-position, >= 0. 51 | */ 52 | int getX(); 53 | 54 | /** 55 | * Returns the Y-position of the cursor. 56 | * 57 | * @return a Y-position, >= 0. 58 | */ 59 | int getY(); 60 | 61 | /** 62 | * Returns whether or not this cursor is visible on screen. 63 | * 64 | * @return true if this cursor is currently visible, 65 | * false otherwise. 66 | */ 67 | boolean isVisible(); 68 | 69 | /** 70 | * Sets the blinking rate of this cursor. 71 | * 72 | * @param rate 73 | * a blinking rate, in milliseconds. A rate of 0 means no blinking. 74 | */ 75 | void setBlinkRate( int rate ); 76 | 77 | /** 78 | * Sets the visibility of the cursor. 79 | * 80 | * @param visible 81 | * true to make the cursor visible, false 82 | * to hide the cursor. 83 | */ 84 | void setVisible( boolean visible ); 85 | } 86 | -------------------------------------------------------------------------------- /src/nl/lxtreme/jvt220/terminal/ITabulator.java: -------------------------------------------------------------------------------- 1 | /** 2 | * jVT220 - Java VT220 terminal emulator. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | package nl.lxtreme.jvt220.terminal; 22 | 23 | 24 | /** 25 | * Provides a tabulator that keeps track of the tab stops of a terminal. 26 | */ 27 | public interface ITabulator 28 | { 29 | // METHODS 30 | 31 | /** 32 | * Clears the tab stop at the given position. 33 | * 34 | * @param position 35 | * the column position used to determine the next tab stop, > 0. 36 | */ 37 | void clear( int position ); 38 | 39 | /** 40 | * Clears all tab stops. 41 | */ 42 | void clearAll(); 43 | 44 | /** 45 | * Returns the width of the tab stop that is at or after the given position. 46 | * 47 | * @param position 48 | * the column position used to determine the next tab stop, >= 0. 49 | * @return the next tab stop width, >= 0. 50 | */ 51 | int getNextTabWidth( int position ); 52 | 53 | /** 54 | * Returns the width of the tab stop that is before the given position. 55 | * 56 | * @param position 57 | * the column position used to determine the previous tab stop, >= 0. 58 | * @return the previous tab stop width, >= 0. 59 | */ 60 | int getPreviousTabWidth( int position ); 61 | 62 | /** 63 | * Returns the next tab stop that is at or after the given position. 64 | * 65 | * @param position 66 | * the column position used to determine the next tab stop, >= 0. 67 | * @return the next tab stop, >= 0. 68 | */ 69 | int nextTab( int position ); 70 | 71 | /** 72 | * Returns the previous tab stop that is before the given position. 73 | * 74 | * @param position 75 | * the column position used to determine the previous tab stop, >= 0. 76 | * @return the previous tab stop, >= 0. 77 | */ 78 | int previousTab( int position ); 79 | 80 | /** 81 | * Sets the tab stop to the given position. 82 | * 83 | * @param position 84 | * the position of the (new) tab stop, > 0. 85 | */ 86 | void set( int position ); 87 | } 88 | -------------------------------------------------------------------------------- /src/nl/lxtreme/jvt220/terminal/ITerminal.java: -------------------------------------------------------------------------------- 1 | /** 2 | * jVT220 - Java VT220 terminal emulator. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | package nl.lxtreme.jvt220.terminal; 22 | 23 | 24 | import java.awt.event.*; 25 | import java.io.*; 26 | 27 | 28 | /** 29 | * Denotes a terminal, which is a text area of fixed dimensions (width and 30 | * height). 31 | */ 32 | public interface ITerminal extends Closeable 33 | { 34 | // INNER TYPES 35 | 36 | /** 37 | * Used to translate keyboard codes to this terminal. 38 | */ 39 | public static interface IKeyMapper 40 | { 41 | // METHODS 42 | 43 | /** 44 | * Maps a given keycode + modifiers mask into a character sequence that can 45 | * be send from this terminal. 46 | * 47 | * @param aKeyCode 48 | * the key code, as defined in {@link KeyEvent} (VK_*); 49 | * @param aModifiers 50 | * the bit mask with key modifiers, as defined in 51 | * {@link InputEvent} (*_MASK). 52 | * @return a string that maps the given key code and modifiers in a sequence 53 | * that is native to this terminal, or null if no 54 | * mapping could be made. 55 | */ 56 | String map( int aKeyCode, int aModifiers ); 57 | } 58 | 59 | /** 60 | * Denotes a single 'cell' which contains a character and mark up attributes. 61 | */ 62 | public static interface ITextCell 63 | { 64 | // METHODS 65 | 66 | /** 67 | * @return the background color index, >= 0 && < 32. A value of 0 means the 68 | * default background color. 69 | */ 70 | int getBackground(); 71 | 72 | /** 73 | * @return the contents of this cell, as UTF-8 character. 74 | */ 75 | char getChar(); 76 | 77 | /** 78 | * @return the foreground color index, >= 0 && < 32. A value of 0 means the 79 | * default background color. 80 | */ 81 | int getForeground(); 82 | 83 | /** 84 | * @return true if the text should be presented in bold, 85 | * false otherwise. 86 | */ 87 | boolean isBold(); 88 | 89 | /** 90 | * @return true if the text should be presented in italic, 91 | * false otherwise. 92 | */ 93 | boolean isItalic(); 94 | 95 | /** 96 | * @return true if the contents of this cell are protected from 97 | * erasure, false otherwise. 98 | */ 99 | boolean isProtected(); 100 | 101 | /** 102 | * @return true if the text should be presented underlined, 103 | * false otherwise. 104 | */ 105 | boolean isUnderline(); 106 | 107 | /** 108 | * @return true if the foreground and background color should 109 | * be swapped, false otherwise. 110 | */ 111 | boolean isReverse(); 112 | 113 | /** 114 | * @return true if the text should be hidden, 115 | * false otherwise. 116 | */ 117 | boolean isHidden(); 118 | 119 | } 120 | 121 | // METHODS 122 | 123 | /** 124 | * Closes this terminal and frees all of its resources. 125 | *

126 | * After a terminal has been closed, it should not be used any more. Doing 127 | * this might result in unexpected behavior. 128 | *

129 | * 130 | * @throws IOException 131 | * in case of I/O problems closing this terminal. 132 | */ 133 | void close() throws IOException; 134 | 135 | /** 136 | * Returns the cursor of this terminal, denoting the current write-position 137 | * is. 138 | * 139 | * @return the current cursor, never null. 140 | */ 141 | ICursor getCursor(); 142 | 143 | /** 144 | * Returns the terminal front end, responsible for representing the contents 145 | * of the terminal visually. 146 | * 147 | * @return the terminal front end, can be null if not set. 148 | */ 149 | ITerminalFrontend getFrontend(); 150 | 151 | /** 152 | * Returns the height of this terminal. 153 | * 154 | * @return a height, in lines. 155 | */ 156 | int getHeight(); 157 | 158 | /** 159 | * Returns a mapper that translated key codes. 160 | * 161 | * @return a key mapper, never null. 162 | */ 163 | IKeyMapper getKeyMapper(); 164 | 165 | /** 166 | * Returns the tabulator for this terminal. 167 | * 168 | * @return the tabulator, never null. 169 | */ 170 | ITabulator getTabulator(); 171 | 172 | /** 173 | * Returns the width of this terminal. 174 | * 175 | * @return a width, in columns. 176 | */ 177 | int getWidth(); 178 | 179 | /** 180 | * Handles the given character sequence as text that should be handled be this 181 | * terminal, for example, by regarding it as literal text, or interpreting in 182 | * some other way. 183 | * 184 | * @param chars 185 | * the character sequence to handle, cannot be null. 186 | * @return the index until which the given character sequence is handled. 187 | * @throws IOException 188 | * in case of I/O exceptions handling the output. 189 | * @see #getCursor() 190 | */ 191 | int read( CharSequence chars ) throws IOException; 192 | 193 | /** 194 | * Resets this terminal to its initial values, meaning that its content will 195 | * be cleared, the cursor will be placed in the first (upper left) position 196 | * and all implementation specific options will be reset to their default 197 | * values. 198 | */ 199 | void reset(); 200 | 201 | /** 202 | * Sets the terminal front end to use. 203 | * 204 | * @param frontend 205 | * the front end to use, cannot be null. 206 | * @throws IllegalArgumentException 207 | * in case the given front end was null. 208 | * @see #getFrontend() 209 | */ 210 | void setFrontend( ITerminalFrontend frontend ); 211 | 212 | /** 213 | * Handles the given character sequence as response from this terminal. 214 | * 215 | * @param chars 216 | * the character sequence to handle, cannot be null. 217 | * @return the index until which the given character sequence is handled. 218 | * @throws IOException 219 | * in case of I/O exceptions handling the input. 220 | */ 221 | int write( CharSequence chars ) throws IOException; 222 | } 223 | -------------------------------------------------------------------------------- /src/nl/lxtreme/jvt220/terminal/ITerminalColorScheme.java: -------------------------------------------------------------------------------- 1 | /** 2 | * jVT220 - Java VT220 terminal emulator. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | package nl.lxtreme.jvt220.terminal; 22 | 23 | 24 | import java.awt.*; 25 | 26 | 27 | /** 28 | * Provides the terminal colors. 29 | */ 30 | public interface ITerminalColorScheme 31 | { 32 | // METHODS 33 | 34 | /** 35 | * Returns the background color. 36 | * 37 | * @return the background color, never null. 38 | * @see #setInverted(boolean) 39 | */ 40 | Color getBackgroundColor(); 41 | 42 | /** 43 | * Returns the foreground color by its numeric index. There are supposed to be 44 | * 8 different foreground colors. 45 | * 46 | * @param aIndex 47 | * the index of the color to return, >= 0 && < 8. 48 | * @return a foreground color, never null. 49 | */ 50 | Color getColorByIndex( int aIndex ); 51 | 52 | /** 53 | * Returns the foreground color. 54 | * 55 | * @return the plain text color, never null. 56 | * @see #setInverted(boolean) 57 | */ 58 | Color getTextColor(); 59 | 60 | /** 61 | * Sets whether or not the foreground and background color are to be swapped. 62 | * 63 | * @param inverted 64 | * true if the background color should become the 65 | * foreground color and the other way around, false 66 | * otherwise. 67 | */ 68 | void setInverted( boolean inverted ); 69 | } 70 | -------------------------------------------------------------------------------- /src/nl/lxtreme/jvt220/terminal/ITerminalFrontend.java: -------------------------------------------------------------------------------- 1 | /** 2 | * jVT220 - Java VT220 terminal emulator. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | package nl.lxtreme.jvt220.terminal; 22 | 23 | 24 | import java.awt.*; 25 | import java.io.*; 26 | import java.util.*; 27 | 28 | import nl.lxtreme.jvt220.terminal.ITerminal.ITextCell; 29 | 30 | 31 | /** 32 | * Denotes a front end for a terminal, which is responsible for representing the 33 | * contents of a terminal visually, e.g, by means of an GUI. 34 | *

35 | * A terminal front end is considered to be responsible for handling the I/O 36 | * between user and terminal. 37 | *

38 | */ 39 | public interface ITerminalFrontend 40 | { 41 | // METHODS 42 | 43 | /** 44 | * Connects this front end to the given input and output streams. 45 | *

46 | * This method will start a background thread to read continuously from the 47 | * given input stream. 48 | *

49 | * 50 | * @param inputStream 51 | * the input stream to read the data that should be passed to the 52 | * terminal, cannot be null; 53 | * @param outputStream 54 | * the output stream to write the data back coming from the terminal, 55 | * cannot be null. 56 | * @throws IOException 57 | * in case of I/O problems connecting the given input and output 58 | * streams. 59 | */ 60 | void connect( InputStream inputStream, OutputStream outputStream ) throws IOException; 61 | 62 | /** 63 | * Connects this frontend to a given output stream. 64 | *

65 | * NOTE: when using this method, you need to explicitly call 66 | * {@link #writeCharacters(Integer...)} yourself in order to let anything 67 | * appear on the terminal. 68 | *

69 | * 70 | * @param outputStream 71 | * the output stream to write the data back coming from the terminal, 72 | * cannot be null. 73 | * @throws IOException 74 | * in case of I/O problems connecting the given output streams. 75 | */ 76 | void connect( OutputStream outputStream ) throws IOException; 77 | 78 | /** 79 | * Disconnects this front end from a connected input and output stream. 80 | * 81 | * @throws IOException 82 | * in case of I/O problems disconnecting. 83 | */ 84 | void disconnect() throws IOException; 85 | 86 | /** 87 | * Returns the maximum possible size of the terminal in columns and lines to 88 | * fit on this frontend. 89 | * 90 | * @return the maximum dimensions, never null. 91 | */ 92 | Dimension getMaximumTerminalSize(); 93 | 94 | /** 95 | * Returns the width and height of the terminal in pixels. 96 | * 97 | * @return the current dimensions, never null. 98 | */ 99 | Dimension getSize(); 100 | 101 | /** 102 | * Returns the {@link Writer} to write responses from the terminal to. 103 | * 104 | * @return a {@link Writer}, can be null in case this front end 105 | * is not connected yet, or disconnected. 106 | */ 107 | Writer getWriter(); 108 | 109 | /** 110 | * @return true if this frontend is able to listen to changes, 111 | * false if not. 112 | */ 113 | boolean isListening(); 114 | 115 | /** 116 | * Sets whether or not the foreground and background colors should be 117 | * reversed. 118 | * 119 | * @param reverse 120 | * true to reverse the foreground and background colors, 121 | * false otherwise. 122 | */ 123 | void setReverse( boolean reverse ); 124 | 125 | /** 126 | * Sets the terminal dimensions in pixels. 127 | * 128 | * @param width 129 | * the new width in pixels, > 0; 130 | * @param height 131 | * the new height in pixels, > 0. 132 | */ 133 | void setSize( int width, int height ); 134 | 135 | /** 136 | * Sets the terminal for this frontend. 137 | * 138 | * @param terminal 139 | * the terminal to connect, cannot be null. 140 | */ 141 | void setTerminal( ITerminal terminal ); 142 | 143 | /** 144 | * Called by {@link ITerminal} to notify this frontend that it has changed. 145 | * 146 | * @param cells 147 | * the array with text cells representing the contents of the 148 | * terminal, never null; 149 | * @param heatMap 150 | * the "heat" map, representing all changed cells of the terminal, 151 | * never null. 152 | */ 153 | void terminalChanged( ITextCell[] cells, BitSet heatMap ); 154 | 155 | /** 156 | * Called by {@link ITerminal} to notify the dimensions of the terminal have 157 | * changed. 158 | * 159 | * @param columns 160 | * the new number of columns, > 0; 161 | * @param alines 162 | * the new number of lines, > 0. 163 | */ 164 | void terminalSizeChanged( int columns, int alines ); 165 | 166 | /** 167 | * Writes the given array of characters directly to the terminal, similar as 168 | * writing to the standard output. 169 | * 170 | * @param chars 171 | * the array with characters to write, cannot be null. 172 | * @throws IOException 173 | * in case of I/O problems writing to the terminal. 174 | */ 175 | void writeCharacters( Integer... chars ) throws IOException; 176 | 177 | /** 178 | * Writes the given sequence of characters directly to the terminal, similar 179 | * as writing to the standard output. 180 | * 181 | * @param chars 182 | * the sequence of characters to write, cannot be null. 183 | * @throws IOException 184 | * in case of I/O problems writing to the terminal. 185 | */ 186 | void writeCharacters( CharSequence chars ) throws IOException; 187 | } 188 | -------------------------------------------------------------------------------- /src/nl/lxtreme/jvt220/terminal/packageinfo: -------------------------------------------------------------------------------- 1 | version 1.1 -------------------------------------------------------------------------------- /src/nl/lxtreme/jvt220/terminal/swing/CharBuffer.java: -------------------------------------------------------------------------------- 1 | /** 2 | * jVT220 - Java VT220 terminal emulator. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | package nl.lxtreme.jvt220.terminal.swing; 22 | 23 | 24 | import java.util.*; 25 | import java.util.concurrent.atomic.*; 26 | 27 | 28 | /** 29 | * Provides a thread-safe character buffer which can append a list of 30 | * characters, or remove a number of characters from the front. 31 | *

32 | * This buffer is designed for atomicity regarding the appending and removal of 33 | * items. There are no ordering guarantees when multiple threads are 34 | * concurrently appending, the only thing that is guaranteed is that the append 35 | * itself is performed atomicly. 36 | *

37 | */ 38 | final class CharBuffer implements CharSequence 39 | { 40 | // INNER TYPES 41 | 42 | /** 43 | * Contains the state of the char buffer itself. 44 | */ 45 | private static class CharBufferState 46 | { 47 | // VARIABLES 48 | 49 | final Integer[] chars; 50 | final int appendPos; 51 | 52 | // CONSTRUCTORS 53 | 54 | /** 55 | * Creates a new {@link CharBufferState} instance. 56 | */ 57 | public CharBufferState( final Integer[] aChars, final int aAppendPos ) 58 | { 59 | this.chars = aChars; 60 | this.appendPos = aAppendPos; 61 | } 62 | } 63 | 64 | // VARIABLES 65 | 66 | private final AtomicReference stateRef; 67 | 68 | // CONSTRUCTORS 69 | 70 | /** 71 | * Creates a new {@link CharBuffer} instance. 72 | */ 73 | public CharBuffer() 74 | { 75 | this.stateRef = new AtomicReference( new CharBufferState( new Integer[10], 0 ) ); 76 | } 77 | 78 | /** 79 | * Creates a new {@link CharBuffer} instance with a given initial value. 80 | * 81 | * @param aInitialValue 82 | * the initial value of this buffer, cannot be null. 83 | * @throws IllegalArgumentException 84 | * in case the given list was null. 85 | */ 86 | public CharBuffer( final Integer... aInitialValue ) 87 | { 88 | if ( aInitialValue == null ) 89 | { 90 | throw new IllegalArgumentException( "InitialValue cannot be null!" ); 91 | } 92 | 93 | Integer[] initialValue = Arrays.copyOf( aInitialValue, aInitialValue.length + 10 ); 94 | int appendPos = aInitialValue.length; 95 | 96 | this.stateRef = new AtomicReference( new CharBufferState( initialValue, appendPos ) ); 97 | } 98 | 99 | // METHODS 100 | 101 | /** 102 | * Appends a given list of characters to this buffer. 103 | * 104 | * @param aChars 105 | * the characters to add, cannot be null. 106 | * @throws IllegalArgumentException 107 | * in case the given list was null. 108 | */ 109 | public void append( final Integer... aChars ) 110 | { 111 | if ( aChars == null ) 112 | { 113 | throw new IllegalArgumentException( "Chars cannot be null!" ); 114 | } 115 | 116 | // Two options for concurrency while we're appending: 117 | // 1) another thread removed data -> append still is valid; 118 | // 2) another thread also appended new data -> append is still valid. 119 | 120 | CharBufferState curState, newState; 121 | 122 | do 123 | { 124 | curState = this.stateRef.get(); 125 | Integer[] curArray = curState.chars; 126 | int curAppendPos = curState.appendPos; 127 | 128 | if ( ( curAppendPos + aChars.length ) >= curArray.length ) 129 | { 130 | // Enlarge array... 131 | curArray = Arrays.copyOf( curArray, curArray.length + aChars.length + 10 ); 132 | } 133 | 134 | System.arraycopy( aChars, 0, curArray, curAppendPos, aChars.length ); 135 | 136 | newState = new CharBufferState( curArray, curAppendPos + aChars.length ); 137 | } 138 | while ( !this.stateRef.compareAndSet( curState, newState ) ); 139 | } 140 | 141 | /** 142 | * {@inheritDoc} 143 | */ 144 | @Override 145 | public char charAt( final int aIndex ) 146 | { 147 | CharBufferState state = this.stateRef.get(); 148 | Integer[] chars = state.chars; 149 | int appendPos = state.appendPos; 150 | 151 | if ( ( aIndex < 0 ) || ( aIndex >= appendPos ) ) 152 | { 153 | throw new IndexOutOfBoundsException(); 154 | } 155 | 156 | final Integer integer = chars[aIndex]; 157 | return ( char )( ( integer == null ) ? 0 : integer.intValue() ); 158 | } 159 | 160 | /** 161 | * {@inheritDoc} 162 | */ 163 | @Override 164 | public int length() 165 | { 166 | CharBufferState state = this.stateRef.get(); 167 | return state.appendPos; 168 | } 169 | 170 | /** 171 | * Removes all characters until (but not including) the given position. 172 | * 173 | * @param aPosition 174 | * the position until which the characters should be removed, with 0 175 | * meaning nothing will be removed, 1 meaning the first character 176 | * will be removed, and so on. 177 | */ 178 | public void removeUntil( final int aPosition ) 179 | { 180 | CharBufferState curState, newState; 181 | int oldAppendPos = -1; 182 | 183 | // Two options for concurrency while we're removing: 184 | // __ 1) another thread appends new data -> remove still is valid; 185 | // __ 2) another thread also removes data: 186 | // ____ a) it removed data up to our position -> remove still is valid; 187 | // ____ b) it removed data beyond our position -> our no longer holds. 188 | 189 | do 190 | { 191 | curState = this.stateRef.get(); 192 | 193 | int position = aPosition; 194 | int curAppendPos = curState.appendPos; 195 | Integer[] curArray = curState.chars; 196 | 197 | if ( oldAppendPos >= 0 ) 198 | { 199 | // CAS failed, find out what the resolution should be... 200 | if ( oldAppendPos > curAppendPos ) 201 | { 202 | // Data is removed, do only remove the difference in positions... 203 | position -= ( oldAppendPos - curAppendPos ); 204 | } 205 | 206 | // In case oldAppendPos < appendPos, there's data appended, which makes 207 | // our remove still valid. In case oldAppendPos == appendPos, then 208 | // something strange is going on, as we've lost our CAS, but neither an 209 | // add, nor an remove is performed. In that case, simply proceed as 210 | // normal... 211 | } 212 | else 213 | { 214 | // First time we're in this loop; do a simple bounds check... 215 | if ( ( position < 0 ) || ( position > curAppendPos ) ) 216 | { 217 | throw new IndexOutOfBoundsException( "Position cannot be negative or beyond the length of this buffer!" ); 218 | } 219 | } 220 | 221 | // Initially, position cannot be less than zero (is checked in the above 222 | // else condition), however, if the CAS fails, we might have updated the 223 | // position thereby making it possible to let it be negative... 224 | if ( position <= 0 ) 225 | { 226 | // Nothing to do... 227 | return; 228 | } 229 | 230 | // Perform the actual removal... 231 | int newSize = Math.max( 0, curArray.length - position ); 232 | Integer[] newArray = new Integer[newSize]; 233 | System.arraycopy( curArray, position, newArray, 0, newSize ); 234 | 235 | int newAppendPos = Math.max( 0, curAppendPos - position ); 236 | 237 | newState = new CharBufferState( newArray, newAppendPos ); 238 | 239 | oldAppendPos = curAppendPos; 240 | } 241 | while ( !this.stateRef.compareAndSet( curState, newState ) ); 242 | } 243 | 244 | /** 245 | * {@inheritDoc} 246 | */ 247 | @Override 248 | public CharSequence subSequence( final int aStart, final int aEnd ) 249 | { 250 | throw new UnsupportedOperationException(); 251 | } 252 | 253 | /** 254 | * {@inheritDoc} 255 | */ 256 | @Override 257 | public String toString() 258 | { 259 | CharBufferState state = this.stateRef.get(); 260 | StringBuilder sb = new StringBuilder(); 261 | for ( int i = 0; i < state.appendPos; i++ ) 262 | { 263 | int c = state.chars[i].intValue(); 264 | if ( c >= ' ' && c <= '~' ) 265 | { 266 | sb.append( ( char )c ); 267 | } 268 | else 269 | { 270 | sb.append( "<" ).append( c ).append( ">" ); 271 | } 272 | } 273 | return sb.toString(); 274 | } 275 | } 276 | -------------------------------------------------------------------------------- /src/nl/lxtreme/jvt220/terminal/swing/SwingFrontend.java: -------------------------------------------------------------------------------- 1 | /** 2 | * jVT220 - Java VT220 terminal emulator. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | package nl.lxtreme.jvt220.terminal.swing; 22 | 23 | 24 | import java.awt.*; 25 | import java.awt.event.*; 26 | import java.awt.font.*; 27 | import java.awt.image.*; 28 | import java.io.*; 29 | import java.text.*; 30 | import java.util.*; 31 | import java.util.List; 32 | 33 | import javax.swing.*; 34 | 35 | import nl.lxtreme.jvt220.terminal.*; 36 | import nl.lxtreme.jvt220.terminal.ITerminal.ITextCell; 37 | 38 | 39 | /** 40 | * Provides a Swing frontend for {@link ITerminal}. 41 | */ 42 | public class SwingFrontend extends JComponent implements ITerminalFrontend 43 | { 44 | // INNER TYPES 45 | 46 | /** 47 | * Small container for the width, height and line spacing of a single 48 | * character. 49 | */ 50 | static final class CharacterDimensions 51 | { 52 | final int m_height; 53 | final int m_width; 54 | final int m_lineSpacing; 55 | 56 | /** 57 | * Creates a new {@link CharacterDimensions} instance. 58 | * 59 | * @param width 60 | * the width of a single character, in pixels; 61 | * @param height 62 | * the height of a single character, in pixels; 63 | * @param lineSpacing 64 | * the spacing to use between two lines with characters, in pixels. 65 | */ 66 | public CharacterDimensions( int width, int height, int lineSpacing ) 67 | { 68 | m_width = width; 69 | m_height = height; 70 | m_lineSpacing = lineSpacing; 71 | } 72 | } 73 | 74 | /** 75 | * Asynchronous worker that reads data from an input stream and passes this to 76 | * the terminal backend. 77 | */ 78 | final class InputStreamWorker extends SwingWorker 79 | { 80 | // VARIABLES 81 | 82 | private final InputStreamReader m_reader; 83 | 84 | // CONSTRUCTORS 85 | 86 | /** 87 | * Creates a new {@link InputStreamWorker} instance. 88 | * 89 | * @param inputStream 90 | * the input stream to read from, cannot be null; 91 | * @param encoding 92 | * the character encoding to use for the read input, cannot be 93 | * null. 94 | */ 95 | public InputStreamWorker( final InputStream inputStream, String encoding ) throws IOException 96 | { 97 | m_reader = new InputStreamReader( inputStream, encoding ); 98 | } 99 | 100 | // METHODS 101 | 102 | @Override 103 | protected Void doInBackground() throws Exception 104 | { 105 | while ( !isCancelled() && !Thread.currentThread().isInterrupted() ) 106 | { 107 | int r = m_reader.read(); 108 | if ( r > 0 ) 109 | { 110 | publish( Integer.valueOf( r ) ); 111 | } 112 | } 113 | return null; 114 | } 115 | 116 | @Override 117 | protected void process( final List readChars ) 118 | { 119 | Integer[] chars = readChars.toArray( new Integer[readChars.size()] ); 120 | 121 | try 122 | { 123 | writeCharacters( chars ); 124 | } 125 | catch ( IOException exception ) 126 | { 127 | exception.printStackTrace(); // XXX 128 | } 129 | } 130 | } 131 | 132 | // CONSTANTS 133 | 134 | /** 135 | * The default encoding to use for the I/O with the outer world. 136 | */ 137 | private static final String ISO8859_1 = "ISO8859-1"; 138 | 139 | // VARIABLES 140 | 141 | private final String m_encoding; 142 | private final CharBuffer m_buffer; 143 | 144 | private ITerminalColorScheme m_colorScheme; 145 | private ICursor m_oldCursor; 146 | private volatile CharacterDimensions m_charDims; 147 | private volatile BufferedImage m_image; 148 | private volatile boolean m_listening; 149 | private ITerminal m_terminal; 150 | private InputStreamWorker m_inputStreamWorker; 151 | private Writer m_writer; 152 | 153 | // CONSTRUCTORS 154 | 155 | /** 156 | * Creates a new {@link SwingFrontend} instance using ISO8859-1 encoding. 157 | */ 158 | public SwingFrontend() 159 | { 160 | this( ISO8859_1 ); 161 | } 162 | 163 | /** 164 | * Creates a new {@link SwingFrontend} instance. 165 | * 166 | * @param encoding 167 | * the character encoding to use for the terminal. 168 | */ 169 | public SwingFrontend( String encoding ) 170 | { 171 | if ( encoding == null || "".equals( encoding.trim() ) ) 172 | { 173 | throw new IllegalArgumentException( "Encoding cannot be null or empty!" ); 174 | } 175 | 176 | m_encoding = encoding; 177 | m_buffer = new CharBuffer(); 178 | m_colorScheme = new XtermColorScheme(); 179 | 180 | setFont( Font.decode( "Monospaced-PLAIN-14" ) ); 181 | 182 | mapKeyboard(); 183 | 184 | setEnabled( false ); 185 | setFocusable( true ); 186 | setFocusTraversalKeysEnabled( false ); // disables TAB handling 187 | requestFocus(); 188 | } 189 | 190 | // METHODS 191 | 192 | /** 193 | * Calculates the character dimensions for the given font, which is presumed 194 | * to be a monospaced font. 195 | * 196 | * @param aFont 197 | * the font to get the character dimensions for, cannot be 198 | * null. 199 | * @return an array of length 2, containing the character width and height (in 200 | * that order). 201 | */ 202 | private static CharacterDimensions getCharacterDimensions( Font aFont ) 203 | { 204 | BufferedImage im = new BufferedImage( 1, 1, BufferedImage.TYPE_INT_ARGB ); 205 | 206 | Graphics2D g2d = im.createGraphics(); 207 | g2d.setFont( aFont ); 208 | FontMetrics fm = g2d.getFontMetrics(); 209 | g2d.dispose(); 210 | im.flush(); 211 | 212 | int w = fm.charWidth( '@' ); 213 | int h = fm.getAscent() + fm.getDescent(); 214 | 215 | return new CharacterDimensions( w, h, fm.getLeading() + 1 ); 216 | } 217 | 218 | /** 219 | * Calculates the union of two rectangles, allowing null values 220 | * to be passed in. 221 | * 222 | * @param rect1 223 | * the 1st rectangle to create the union, if null, the 224 | * 2nd argument will be returned; 225 | * @param rect2 226 | * the 2nd rectangle to create the union, if null, the 227 | * 1st argument will be returned. 228 | * @return the union of the two given rectangles. 229 | */ 230 | private static Rectangle union( Rectangle rect1, Rectangle rect2 ) 231 | { 232 | if ( rect2 == null ) 233 | { 234 | return rect1; 235 | } 236 | if ( rect1 == null ) 237 | { 238 | return rect2; 239 | } 240 | 241 | return rect1.union( rect2 ); 242 | } 243 | 244 | /** 245 | * Connects this frontend to a given input and output stream. 246 | *

247 | * This method will start a background thread to read continuously from the 248 | * given input stream. 249 | *

250 | * 251 | * @param inputStream 252 | * the input stream to connect to, cannot be null; 253 | * @param outputStream 254 | * the output stream to connect to, cannot be null. 255 | * @throws IOException 256 | * in case of I/O problems. 257 | */ 258 | @Override 259 | public void connect( InputStream inputStream, OutputStream outputStream ) throws IOException 260 | { 261 | if ( inputStream == null ) 262 | { 263 | throw new IllegalArgumentException( "Input stream cannot be null!" ); 264 | } 265 | if ( outputStream == null ) 266 | { 267 | throw new IllegalArgumentException( "Output stream cannot be null!" ); 268 | } 269 | 270 | disconnect(); 271 | 272 | m_writer = new OutputStreamWriter( outputStream, m_encoding ); 273 | 274 | m_inputStreamWorker = new InputStreamWorker( inputStream, m_encoding ); 275 | m_inputStreamWorker.execute(); 276 | 277 | setEnabled( true ); 278 | } 279 | 280 | /** 281 | * Connects this frontend to a given output stream. 282 | *

283 | * NOTE: when using this method, you need to explicitly call 284 | * {@link #writeCharacters(Integer...)} yourself in order to let anything 285 | * appear on the terminal. 286 | *

287 | * 288 | * @param outputStream 289 | * the output stream to connect to, cannot be null. 290 | * @throws IOException 291 | * in case of I/O problems. 292 | */ 293 | @Override 294 | public void connect( OutputStream outputStream ) throws IOException 295 | { 296 | if ( outputStream == null ) 297 | { 298 | throw new IllegalArgumentException( "Output stream cannot be null!" ); 299 | } 300 | 301 | disconnect(); 302 | 303 | m_writer = new OutputStreamWriter( outputStream, m_encoding ); 304 | 305 | setEnabled( true ); 306 | } 307 | 308 | /** 309 | * Disconnects this frontend from any input and output stream. 310 | * 311 | * @throws IOException 312 | * in case of I/O problems. 313 | */ 314 | @Override 315 | public void disconnect() throws IOException 316 | { 317 | try 318 | { 319 | if ( m_inputStreamWorker != null ) 320 | { 321 | m_inputStreamWorker.cancel( true /* mayInterruptIfRunning */); 322 | m_inputStreamWorker = null; 323 | } 324 | if ( m_writer != null ) 325 | { 326 | m_writer.close(); 327 | m_writer = null; 328 | } 329 | } 330 | finally 331 | { 332 | setEnabled( false ); 333 | } 334 | } 335 | 336 | /** 337 | * {@inheritDoc} 338 | */ 339 | @Override 340 | public Dimension getMaximumTerminalSize() 341 | { 342 | Rectangle bounds = getGraphicsConfiguration().getBounds(); 343 | Insets insets = calculateTotalInsets(); 344 | 345 | int width = bounds.width - insets.left - insets.right; 346 | int height = bounds.height - insets.top - insets.bottom; 347 | 348 | CharacterDimensions charDims = m_charDims; 349 | 350 | // Calculate the maximum number of columns & lines... 351 | int columns = width / charDims.m_width; 352 | int lines = height / ( charDims.m_height + charDims.m_lineSpacing ); 353 | 354 | return new Dimension( columns, lines ); 355 | } 356 | 357 | /** 358 | * Returns the current terminal. 359 | * 360 | * @return the terminal, can be null. 361 | */ 362 | public ITerminal getTerminal() 363 | { 364 | return m_terminal; 365 | } 366 | 367 | /** 368 | * {@inheritDoc} 369 | */ 370 | @Override 371 | public Writer getWriter() 372 | { 373 | return m_writer; 374 | } 375 | 376 | /** 377 | * {@inheritDoc} 378 | */ 379 | @Override 380 | public boolean isListening() 381 | { 382 | return m_listening; 383 | } 384 | 385 | /** 386 | * {@inheritDoc} 387 | */ 388 | @Override 389 | public void setFont( Font font ) 390 | { 391 | super.setFont( font ); 392 | 393 | m_charDims = getCharacterDimensions( font ); 394 | } 395 | 396 | /** 397 | * @see nl.lxtreme.jvt220.terminal.ITerminalFrontend#setReverse(boolean) 398 | */ 399 | @Override 400 | public void setReverse( boolean reverse ) 401 | { 402 | m_colorScheme.setInverted( reverse ); 403 | } 404 | 405 | /** 406 | * Sets the size of this component in pixels. Overridden in order to redirect 407 | * this call to {@link #terminalSizeChanged(int, int)} with the correct number 408 | * of columns and lines. 409 | */ 410 | @Override 411 | public void setSize( int width, int height ) 412 | { 413 | Rectangle bounds = getGraphicsConfiguration().getBounds(); 414 | Insets insets = calculateTotalInsets(); 415 | 416 | if ( width == 0 ) 417 | { 418 | width = bounds.width - insets.left - insets.right; 419 | } 420 | else if ( width < 0 ) 421 | { 422 | width = getWidth(); 423 | } 424 | if ( height == 0 ) 425 | { 426 | height = bounds.height - insets.top - insets.bottom; 427 | } 428 | else if ( height < 0 ) 429 | { 430 | height = getHeight(); 431 | } 432 | 433 | CharacterDimensions charDims = m_charDims; 434 | 435 | int columns = width / charDims.m_width; 436 | int lines = height / ( charDims.m_height + charDims.m_lineSpacing ); 437 | 438 | terminalSizeChanged( columns, lines ); 439 | } 440 | 441 | /** 442 | * {@inheritDoc} 443 | */ 444 | @Override 445 | public void setTerminal( ITerminal terminal ) 446 | { 447 | if ( terminal == null ) 448 | { 449 | throw new IllegalArgumentException( "Terminal cannot be null!" ); 450 | } 451 | m_terminal = terminal; 452 | m_terminal.setFrontend( this ); 453 | } 454 | 455 | /** 456 | * {@inheritDoc} 457 | */ 458 | @Override 459 | public void terminalChanged( final ITextCell[] cells, final BitSet heatMap ) 460 | { 461 | SwingUtilities.invokeLater( new Runnable() 462 | { 463 | @Override 464 | public void run() 465 | { 466 | updateTerminalImage( cells, heatMap ); 467 | } 468 | } ); 469 | } 470 | 471 | /** 472 | * {@inheritDoc} 473 | */ 474 | @Override 475 | public void terminalSizeChanged( final int columns, final int lines ) 476 | { 477 | SwingUtilities.invokeLater( new Runnable() 478 | { 479 | @Override 480 | public void run() 481 | { 482 | recreateTerminalImage( columns, lines, true /* forceRepaint */ ); 483 | } 484 | } ); 485 | } 486 | 487 | /** 488 | * Writes the given sequence of characters directly to the terminal, similar 489 | * as writing to the standard output. 490 | * 491 | * @param charSeq 492 | * the sequence of characters to write, cannot be null. 493 | * @throws IOException 494 | * in case of I/O problems writing to the terminal. 495 | */ 496 | @Override 497 | public void writeCharacters( CharSequence charSeq ) throws IOException 498 | { 499 | InputStream is = new ByteArrayInputStream( charSeq.toString().getBytes() ); 500 | InputStreamReader isr = new InputStreamReader( is, m_encoding ); 501 | 502 | int ch; 503 | while ( ( ch = isr.read() ) >= 0 ) 504 | { 505 | writeCharacters( ch ); 506 | } 507 | } 508 | 509 | /** 510 | * Writes the given array of characters directly to the terminal, similar as 511 | * writing to the standard output. 512 | * 513 | * @param chars 514 | * the characters to write, cannot be null. 515 | * @throws IOException 516 | * in case of I/O problems writing to the terminal. 517 | */ 518 | @Override 519 | public void writeCharacters( Integer... chars ) throws IOException 520 | { 521 | m_buffer.append( chars ); 522 | 523 | int n = m_terminal.read( m_buffer ); 524 | 525 | m_buffer.removeUntil( n ); 526 | } 527 | 528 | /** 529 | * Recreates the terminal image. 530 | * 531 | * @param columns 532 | * the new number of columns, > 0; 533 | * @param lines 534 | * the new number of lines, > 0. 535 | */ 536 | void recreateTerminalImage( int columns, int lines, boolean forceRepaint ) 537 | { 538 | assert SwingUtilities.isEventDispatchThread() : "Should be called from EDT only!"; 539 | 540 | final Dimension dims = calculateSizeInPixels( columns, lines ); 541 | 542 | if ( ( m_image == null ) || ( m_image.getWidth() != dims.width ) || ( m_image.getHeight() != dims.height ) ) 543 | { 544 | if ( m_image != null ) 545 | { 546 | m_image.flush(); 547 | } 548 | m_image = getGraphicsConfiguration().createCompatibleImage( dims.width, dims.height ); 549 | 550 | Graphics2D canvas = m_image.createGraphics(); 551 | 552 | try 553 | { 554 | canvas.setBackground( m_colorScheme.getBackgroundColor() ); 555 | canvas.clearRect( 0, 0, m_image.getWidth(), m_image.getHeight() ); 556 | } 557 | finally 558 | { 559 | canvas.dispose(); 560 | canvas = null; 561 | } 562 | 563 | // Update the size of this component as well... 564 | Insets insets = getInsets(); 565 | super.setSize( dims.width + insets.left + insets.right, dims.height + insets.top + insets.bottom ); 566 | 567 | repaint( 50L ); 568 | } 569 | } 570 | 571 | /** 572 | * Updates the image representing the terminal contents. 573 | * 574 | * @param cells 575 | * the text cells; 576 | * @param heatMap 577 | * the heat map denoting the changed cells. 578 | */ 579 | void updateTerminalImage( ITextCell[] cells, BitSet heatMap ) 580 | { 581 | assert SwingUtilities.isEventDispatchThread() : "Should be called from the EDT only!"; 582 | 583 | final int columns = m_terminal.getWidth(); 584 | final int lines = m_terminal.getHeight(); 585 | 586 | // Create copies of these data items to ensure they remain constant for 587 | // the remainder of this method... 588 | CharacterDimensions charDims = m_charDims; 589 | 590 | int cw = charDims.m_width; 591 | int ch = charDims.m_height; 592 | int ls = charDims.m_lineSpacing; 593 | 594 | if ( m_image == null ) 595 | { 596 | // Ensure there's a valid image to paint on... 597 | recreateTerminalImage( columns, lines, false /* forceRepaint */ ); 598 | } 599 | 600 | final Graphics2D canvas = m_image.createGraphics(); 601 | canvas.setFont( getFont() ); 602 | 603 | final Font font = getFont(); 604 | final FontMetrics fm = canvas.getFontMetrics(); 605 | final FontRenderContext frc = new FontRenderContext( null, true /* aa */, true /* fractionalMetrics */); 606 | 607 | Color cursorColor = null; 608 | Rectangle repaintArea = null; 609 | 610 | if ( m_oldCursor != null ) 611 | { 612 | repaintArea = drawCursor( canvas, m_oldCursor, m_colorScheme.getBackgroundColor() ); 613 | } 614 | 615 | for ( int i = 0; i < cells.length; i++ ) 616 | { 617 | boolean cellChanged = heatMap.get( i ); 618 | if ( cellChanged ) 619 | { 620 | // Cell is changed... 621 | final ITextCell cell = cells[i]; 622 | 623 | final int x = ( i % columns ) * cw; 624 | final int y = ( i / columns ) * ( ch + ls ); 625 | 626 | final Rectangle rect = new Rectangle( x, y, cw, ch + ls ); 627 | 628 | canvas.setColor( convertToColor( cell.getBackground(), m_colorScheme.getBackgroundColor() ) ); 629 | canvas.fillRect( rect.x, rect.y, rect.width, rect.height ); 630 | 631 | final String txt = Character.toString( cell.getChar() ); 632 | 633 | AttributedString attrStr = new AttributedString( txt ); 634 | cursorColor = applyAttributes( cell, attrStr, font ); 635 | 636 | AttributedCharacterIterator characterIterator = attrStr.getIterator(); 637 | LineBreakMeasurer measurer = new LineBreakMeasurer( characterIterator, frc ); 638 | 639 | while ( measurer.getPosition() < characterIterator.getEndIndex() ) 640 | { 641 | TextLayout textLayout = measurer.nextLayout( getWidth() ); 642 | textLayout.draw( canvas, x, y + fm.getAscent() ); 643 | } 644 | 645 | repaintArea = union( repaintArea, rect ); 646 | } 647 | } 648 | 649 | // Draw the cursor... 650 | m_oldCursor = m_terminal.getCursor().clone(); 651 | if ( cursorColor == null ) 652 | { 653 | cursorColor = m_colorScheme.getTextColor(); 654 | } 655 | 656 | repaintArea = union( repaintArea, drawCursor( canvas, m_oldCursor, cursorColor ) ); 657 | 658 | // Free the resources... 659 | canvas.dispose(); 660 | 661 | if ( repaintArea != null ) 662 | { 663 | repaintArea.grow( 5, 3 ); 664 | repaint( repaintArea ); 665 | } 666 | } 667 | 668 | /** 669 | * Maps the keyboard to respond to keys like 'up', 'down' and the function 670 | * keys. 671 | */ 672 | protected void mapKeyboard() 673 | { 674 | mapKeystroke( KeyEvent.VK_UP ); 675 | mapKeystroke( KeyEvent.VK_DOWN ); 676 | mapKeystroke( KeyEvent.VK_RIGHT ); 677 | mapKeystroke( KeyEvent.VK_LEFT ); 678 | 679 | mapKeystroke( KeyEvent.VK_PAGE_DOWN ); 680 | mapKeystroke( KeyEvent.VK_PAGE_UP ); 681 | mapKeystroke( KeyEvent.VK_HOME ); 682 | mapKeystroke( KeyEvent.VK_END ); 683 | 684 | mapKeystroke( KeyEvent.VK_NUMPAD0 ); 685 | mapKeystroke( KeyEvent.VK_NUMPAD1 ); 686 | mapKeystroke( KeyEvent.VK_NUMPAD2 ); 687 | mapKeystroke( KeyEvent.VK_NUMPAD3 ); 688 | mapKeystroke( KeyEvent.VK_NUMPAD4 ); 689 | mapKeystroke( KeyEvent.VK_NUMPAD5 ); 690 | mapKeystroke( KeyEvent.VK_NUMPAD6 ); 691 | mapKeystroke( KeyEvent.VK_NUMPAD7 ); 692 | mapKeystroke( KeyEvent.VK_NUMPAD8 ); 693 | mapKeystroke( KeyEvent.VK_NUMPAD9 ); 694 | mapKeystroke( KeyEvent.VK_MINUS ); 695 | mapKeystroke( KeyEvent.VK_PLUS ); 696 | mapKeystroke( KeyEvent.VK_COMMA ); 697 | mapKeystroke( KeyEvent.VK_PERIOD ); 698 | mapKeystroke( KeyEvent.VK_ENTER ); 699 | mapKeystroke( KeyEvent.VK_KP_DOWN ); 700 | mapKeystroke( KeyEvent.VK_KP_LEFT ); 701 | mapKeystroke( KeyEvent.VK_KP_RIGHT ); 702 | mapKeystroke( KeyEvent.VK_KP_UP ); 703 | 704 | mapKeystroke( KeyEvent.VK_F1 ); 705 | mapKeystroke( KeyEvent.VK_F1, InputEvent.ALT_DOWN_MASK ); 706 | mapKeystroke( KeyEvent.VK_F2 ); 707 | mapKeystroke( KeyEvent.VK_F2, InputEvent.ALT_DOWN_MASK ); 708 | mapKeystroke( KeyEvent.VK_F3 ); 709 | mapKeystroke( KeyEvent.VK_F3, InputEvent.ALT_DOWN_MASK ); 710 | mapKeystroke( KeyEvent.VK_F4 ); 711 | mapKeystroke( KeyEvent.VK_F4, InputEvent.ALT_DOWN_MASK ); 712 | mapKeystroke( KeyEvent.VK_F5 ); 713 | mapKeystroke( KeyEvent.VK_F6 ); 714 | mapKeystroke( KeyEvent.VK_F7 ); 715 | mapKeystroke( KeyEvent.VK_F8 ); 716 | mapKeystroke( KeyEvent.VK_F9 ); 717 | mapKeystroke( KeyEvent.VK_F10 ); 718 | mapKeystroke( KeyEvent.VK_F11 ); 719 | mapKeystroke( KeyEvent.VK_F12 ); 720 | } 721 | 722 | /** 723 | * Maps the given keycode and modifiers to a terminal specific sequence. 724 | * 725 | * @param keycode 726 | * the keycode to map; 727 | * @param modifiers 728 | * the modifiers to map. 729 | * @return a terminal-specific sequence for the given input, or 730 | * null if no specific sequence exists for this terminal. 731 | */ 732 | protected String mapKeyCode( int keycode, int modifiers ) 733 | { 734 | return getTerminal().getKeyMapper().map( keycode, modifiers ); 735 | } 736 | 737 | /** 738 | * Creates a key mapping for the given keystroke and the given action which is 739 | * send as literal text to the terminal. 740 | * 741 | * @param keycode 742 | * the keycode to map. 743 | */ 744 | protected void mapKeystroke( int keycode ) 745 | { 746 | mapKeystroke( keycode, 0 ); 747 | } 748 | 749 | /** 750 | * Creates a key mapping for the given keystroke and the given action which is 751 | * send as literal text to the terminal. 752 | * 753 | * @param keycode 754 | * the keycode to map; 755 | * @param modifiers 756 | * the modifiers to map. 757 | */ 758 | protected void mapKeystroke( int keycode, int modifiers ) 759 | { 760 | final KeyStroke keystroke = KeyStroke.getKeyStroke( keycode, modifiers ); 761 | final String key = keystroke.toString(); 762 | 763 | getInputMap().put( keystroke, key ); 764 | } 765 | 766 | /** 767 | * {@inheritDoc} 768 | */ 769 | @Override 770 | protected void paintComponent( Graphics canvas ) 771 | { 772 | m_listening = false; 773 | 774 | canvas.setColor( m_colorScheme.getBackgroundColor() ); 775 | 776 | Rectangle clip = canvas.getClipBounds(); 777 | canvas.fillRect( clip.x, clip.y, clip.width, clip.height ); 778 | 779 | try 780 | { 781 | Insets insets = getInsets(); 782 | 783 | canvas.drawImage( m_image, insets.left, insets.top, null /* observer */); 784 | } 785 | finally 786 | { 787 | m_listening = true; 788 | } 789 | } 790 | 791 | /** 792 | * {@inheritDoc} 793 | */ 794 | @Override 795 | protected boolean processKeyBinding( KeyStroke keystroke, KeyEvent event, int condition, boolean pressed ) 796 | { 797 | if ( !isEnabled() ) 798 | { 799 | // Don't bother to process keys for a disabled component... 800 | return false; 801 | } 802 | 803 | InputMap inputMap = getInputMap( condition ); 804 | ActionMap actionMap = getActionMap(); 805 | 806 | try 807 | { 808 | if ( ( inputMap != null ) && ( actionMap != null ) && ( event.getID() == KeyEvent.KEY_PRESSED ) ) 809 | { 810 | Object binding = inputMap.get( keystroke ); 811 | if ( binding != null ) 812 | { 813 | Action action = actionMap.get( binding ); 814 | if ( action != null ) 815 | { 816 | // Normal action; invoke it... 817 | return SwingUtilities.notifyAction( action, keystroke, event, this, event.getModifiers() ); 818 | } 819 | else 820 | { 821 | // Keystroke we've mapped without an action, means we're going to 822 | // test whether there's a mapping for it. If so, use that as 823 | // response, otherwise respond with the 'regular' key... 824 | String mapping = mapKeyCode( keystroke.getKeyCode(), keystroke.getModifiers() ); 825 | if ( mapping != null ) 826 | { 827 | respond( mapping ); 828 | return true; 829 | } 830 | } 831 | } 832 | 833 | if ( isRegularKey( keystroke ) ) 834 | { 835 | respond( event.getKeyChar() ); 836 | return true; 837 | } 838 | } 839 | } 840 | catch ( IOException exception ) 841 | { 842 | exception.printStackTrace(); // XXX 843 | } 844 | 845 | return false; 846 | } 847 | 848 | /** 849 | * Writes a given number of characters to the terminal. 850 | * 851 | * @param chars 852 | * the characters to write, cannot be null. 853 | * @throws IOException 854 | * in case of I/O problems responding. 855 | */ 856 | protected void respond( char ch ) throws IOException 857 | { 858 | if ( m_writer != null ) 859 | { 860 | m_writer.write( ch ); 861 | m_writer.flush(); 862 | } 863 | } 864 | 865 | /** 866 | * Writes a given number of characters to the terminal. 867 | * 868 | * @param chars 869 | * the characters to write, cannot be null. 870 | * @throws IOException 871 | * in case of I/O problems responding. 872 | */ 873 | protected void respond( String chars ) throws IOException 874 | { 875 | if ( m_writer != null ) 876 | { 877 | m_writer.write( chars ); 878 | m_writer.flush(); 879 | } 880 | } 881 | 882 | /** 883 | * Applies the attributes from the given {@link TextCell} to the given 884 | * {@link AttributedString}. 885 | * 886 | * @param textCell 887 | * the text cell to get the attributes from; 888 | * @param attributedString 889 | * the {@link AttributedString} to apply the attributes to; 890 | * @param font 891 | * the font to use. 892 | * @return the primary foreground color, never null. 893 | */ 894 | private Color applyAttributes( ITextCell textCell, AttributedString attributedString, Font font ) 895 | { 896 | Color fg = convertToColor( textCell.getForeground(), m_colorScheme.getTextColor() ); 897 | Color bg = convertToColor( textCell.getBackground(), m_colorScheme.getBackgroundColor() ); 898 | 899 | attributedString.addAttribute( TextAttribute.FAMILY, font.getFamily() ); 900 | attributedString.addAttribute( TextAttribute.SIZE, font.getSize() ); 901 | attributedString.addAttribute( TextAttribute.FOREGROUND, textCell.isReverse() ^ textCell.isHidden() ? bg : fg ); 902 | attributedString.addAttribute( TextAttribute.BACKGROUND, textCell.isReverse() ? fg : bg ); 903 | 904 | if ( textCell.isUnderline() ) 905 | { 906 | attributedString.addAttribute( TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON ); 907 | } 908 | if ( textCell.isBold() ) 909 | { 910 | attributedString.addAttribute( TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD ); 911 | } 912 | if ( textCell.isItalic() ) 913 | { 914 | attributedString.addAttribute( TextAttribute.POSTURE, TextAttribute.POSTURE_OBLIQUE ); 915 | } 916 | return textCell.isReverse() ^ textCell.isHidden() ? bg : fg; 917 | } 918 | 919 | /** 920 | * Calculates the size (in pixels) of the back buffer image. 921 | * 922 | * @param columns 923 | * the number of columns, > 0; 924 | * @param lines 925 | * the number of lines, > 0. 926 | * @return a dimension with the image width and height in pixels. 927 | */ 928 | private Dimension calculateSizeInPixels( int columns, int lines ) 929 | { 930 | CharacterDimensions charDims = m_charDims; 931 | 932 | int width = ( columns * charDims.m_width ); 933 | int height = ( lines * ( charDims.m_height + charDims.m_lineSpacing ) ); 934 | return new Dimension( width, height ); 935 | } 936 | 937 | /** 938 | * Calculates the total insets of this container and all of its parents. 939 | * 940 | * @return the total insets, never null. 941 | */ 942 | private Insets calculateTotalInsets() 943 | { 944 | // Take the screen insets as starting point... 945 | Insets insets = Toolkit.getDefaultToolkit().getScreenInsets( getGraphicsConfiguration() ); 946 | 947 | Container ptr = this; 948 | do 949 | { 950 | Insets compInsets = ptr.getInsets(); 951 | insets.top += compInsets.top; 952 | insets.bottom += compInsets.bottom; 953 | insets.left += compInsets.left; 954 | insets.right += compInsets.right; 955 | ptr = ptr.getParent(); 956 | } 957 | while ( ptr != null ); 958 | return insets; 959 | } 960 | 961 | /** 962 | * Converts a given color index to a concrete color value. 963 | * 964 | * @param index 965 | * the numeric color index, >= 0; 966 | * @param defaultColor 967 | * the default color to use, cannot be null. 968 | * @return a color value, never null. 969 | */ 970 | private Color convertToColor( int index, Color defaultColor ) 971 | { 972 | if ( index < 1 ) 973 | { 974 | return defaultColor; 975 | } 976 | return m_colorScheme.getColorByIndex( index - 1 ); 977 | } 978 | 979 | /** 980 | * Draws the cursor on screen. 981 | * 982 | * @param canvas 983 | * the canvas to paint on; 984 | * @param cursor 985 | * the cursor information; 986 | * @param color 987 | * the color to paint the cursor in. 988 | */ 989 | private Rectangle drawCursor( final Graphics2D canvas, final ICursor cursor, final Color color ) 990 | { 991 | if ( !cursor.isVisible() ) 992 | { 993 | return null; 994 | } 995 | 996 | CharacterDimensions charDims = m_charDims; 997 | 998 | int cw = charDims.m_width; 999 | int ch = charDims.m_height; 1000 | int ls = charDims.m_lineSpacing; 1001 | 1002 | int x = cursor.getX() * cw; 1003 | int y = cursor.getY() * ( ch + ls ); 1004 | 1005 | Rectangle rect = new Rectangle( x, y, cw, ch - 2 * ls ); 1006 | 1007 | canvas.setColor( color ); 1008 | canvas.draw( rect ); 1009 | 1010 | return rect; 1011 | } 1012 | 1013 | /** 1014 | * Returns whether the given keystroke represents a "regular" key, that is, it 1015 | * is defined and not a modifier. 1016 | * 1017 | * @param keystroke 1018 | * the keystroke to test, cannot be null. 1019 | * @return false if the given keystroke is either not defined or 1020 | * represents a modifier key (SHIFT, CTRL, etc.), true 1021 | * otherwise. 1022 | */ 1023 | private boolean isRegularKey( KeyStroke keystroke ) 1024 | { 1025 | int c = keystroke.getKeyCode(); 1026 | return ( c != KeyEvent.VK_UNDEFINED ) && ( c != KeyEvent.VK_SHIFT ) && ( c != KeyEvent.VK_ALT ) 1027 | && ( c != KeyEvent.VK_ALT_GRAPH ) && ( c != KeyEvent.VK_META ) && ( c != KeyEvent.VK_WINDOWS ) 1028 | && ( c != KeyEvent.VK_CONTROL ); 1029 | } 1030 | } 1031 | -------------------------------------------------------------------------------- /src/nl/lxtreme/jvt220/terminal/swing/XtermColorScheme.java: -------------------------------------------------------------------------------- 1 | /** 2 | * jVT220 - Java VT220 terminal emulator. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | package nl.lxtreme.jvt220.terminal.swing; 22 | 23 | 24 | import java.awt.*; 25 | import java.util.concurrent.atomic.*; 26 | 27 | import nl.lxtreme.jvt220.terminal.*; 28 | 29 | 30 | /** 31 | * Represents the default color scheme, derived from the one used by XTerm. 32 | */ 33 | public final class XtermColorScheme implements ITerminalColorScheme 34 | { 35 | // CONSTANTS 36 | 37 | private static final Color PLAIN_TEXT_COLOR = new Color( 0xE6, 0xE6, 0xE6 ); 38 | private static final Color BACKGROUND_COLOR = new Color( 0x1E, 0x21, 0x26 ); 39 | 40 | private static final Color[] XTERM_COLORS = { BACKGROUND_COLOR, // Off-Black 41 | new Color( 205, 0, 0 ), // Red 42 | new Color( 0, 205, 0 ), // Green 43 | new Color( 205, 205, 0 ), // Yellow 44 | new Color( 0, 0, 238 ), // Blue 45 | new Color( 205, 0, 205 ), // Magenta 46 | new Color( 0, 205, 205 ), // Cyan 47 | new Color( 229, 229, 229 ), // White 48 | }; 49 | 50 | // VARIABLES 51 | 52 | private final AtomicBoolean inverted; 53 | 54 | // CONSTRUCTORS 55 | 56 | /** 57 | * Creates a new {@link XtermColorScheme} instance. 58 | */ 59 | public XtermColorScheme() 60 | { 61 | this.inverted = new AtomicBoolean( false ); 62 | } 63 | 64 | // METHODS 65 | 66 | /** 67 | * {@inheritDoc} 68 | */ 69 | @Override 70 | public Color getBackgroundColor() 71 | { 72 | return isInverted() ? PLAIN_TEXT_COLOR : BACKGROUND_COLOR; 73 | } 74 | 75 | /** 76 | * {@inheritDoc} 77 | */ 78 | @Override 79 | public Color getColorByIndex( int aIndex ) 80 | { 81 | return XTERM_COLORS[aIndex % XTERM_COLORS.length]; 82 | } 83 | 84 | /** 85 | * {@inheritDoc} 86 | */ 87 | @Override 88 | public Color getTextColor() 89 | { 90 | return isInverted() ? BACKGROUND_COLOR : PLAIN_TEXT_COLOR; 91 | } 92 | 93 | /** 94 | * @return true if the foreground and background colors are to be 95 | * swapped, false otherwise. 96 | */ 97 | public boolean isInverted() 98 | { 99 | return this.inverted.get(); 100 | } 101 | 102 | /** 103 | * {@inheritDoc} 104 | */ 105 | @Override 106 | public void setInverted( boolean aInverted ) 107 | { 108 | this.inverted.set( aInverted ); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/nl/lxtreme/jvt220/terminal/swing/packageinfo: -------------------------------------------------------------------------------- 1 | version 1.1.1 2 | -------------------------------------------------------------------------------- /src/nl/lxtreme/jvt220/terminal/vt220/AbstractTerminal.java: -------------------------------------------------------------------------------- 1 | /** 2 | * jVT220 - Java VT220 terminal emulator. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | package nl.lxtreme.jvt220.terminal.vt220; 22 | 23 | 24 | import java.io.*; 25 | import java.util.*; 26 | 27 | import nl.lxtreme.jvt220.terminal.*; 28 | 29 | 30 | /** 31 | * Provides an abstract base implementation of {@link ITerminal}. 32 | */ 33 | public abstract class AbstractTerminal implements ITerminal 34 | { 35 | // INNER TYPES 36 | 37 | /** 38 | * Provides a default key mapper that does not map anything. 39 | */ 40 | protected static class DefaultKeyMapper implements IKeyMapper 41 | { 42 | // METHODS 43 | 44 | /** 45 | * {@inheritDoc} 46 | */ 47 | @Override 48 | public String map( int keyCode, int modifiers ) 49 | { 50 | return null; 51 | } 52 | } 53 | 54 | /** 55 | * Provides a default tabulator implementation. 56 | */ 57 | protected class DefaultTabulator implements ITabulator 58 | { 59 | // CONSTANTS 60 | 61 | private static final int DEFAULT_TABSTOP = 8; 62 | 63 | // VARIABLES 64 | 65 | private final SortedSet m_tabStops; 66 | 67 | // CONSTRUCTORS 68 | 69 | /** 70 | * Creates a new {@link DefaultTabulator} instance. 71 | * 72 | * @param columns 73 | * the number of colums, for example, 80. 74 | */ 75 | public DefaultTabulator( int columns ) 76 | { 77 | this( columns, DEFAULT_TABSTOP ); 78 | } 79 | 80 | /** 81 | * Creates a new {@link DefaultTabulator} instance. 82 | * 83 | * @param columns 84 | * the number of colums, for example, 80; 85 | * @param tabStop 86 | * the default tab stop to use, for example, 8. 87 | */ 88 | public DefaultTabulator( int columns, int tabStop ) 89 | { 90 | m_tabStops = new TreeSet(); 91 | for ( int i = tabStop; i < columns; i += tabStop ) 92 | { 93 | m_tabStops.add( Integer.valueOf( i ) ); 94 | } 95 | } 96 | 97 | // METHODS 98 | 99 | /** 100 | * {@inheritDoc} 101 | */ 102 | @Override 103 | public void clear( int position ) 104 | { 105 | m_tabStops.remove( Integer.valueOf( position ) ); 106 | } 107 | 108 | /** 109 | * {@inheritDoc} 110 | */ 111 | @Override 112 | public void clearAll() 113 | { 114 | m_tabStops.clear(); 115 | } 116 | 117 | /** 118 | * {@inheritDoc} 119 | */ 120 | @Override 121 | public int getNextTabWidth( int position ) 122 | { 123 | return nextTab( position ) - position; 124 | } 125 | 126 | /** 127 | * {@inheritDoc} 128 | */ 129 | @Override 130 | public int getPreviousTabWidth( int position ) 131 | { 132 | return position - previousTab( position ); 133 | } 134 | 135 | /** 136 | * @return the current tab stops, never null. 137 | */ 138 | public SortedSet getTabStops() 139 | { 140 | return m_tabStops; 141 | } 142 | 143 | /** 144 | * {@inheritDoc} 145 | */ 146 | @Override 147 | public int nextTab( int position ) 148 | { 149 | int tabStop = Integer.MAX_VALUE; 150 | 151 | // Search for the first tab stop after the given position... 152 | SortedSet tailSet = m_tabStops.tailSet( Integer.valueOf( position + 1 ) ); 153 | if ( !tailSet.isEmpty() ) 154 | { 155 | tabStop = tailSet.first(); 156 | } 157 | 158 | // Don't go beyond the end of the line... 159 | return Math.min( tabStop, ( getWidth() - 1 ) ); 160 | } 161 | 162 | /** 163 | * {@inheritDoc} 164 | */ 165 | @Override 166 | public int previousTab( int position ) 167 | { 168 | int tabStop = 0; 169 | 170 | // Search for the first tab stop before the given position... 171 | SortedSet headSet = m_tabStops.headSet( Integer.valueOf( position ) ); 172 | if ( !headSet.isEmpty() ) 173 | { 174 | tabStop = headSet.last(); 175 | } 176 | 177 | // Don't go beyond the start of the line... 178 | return Math.max( 0, tabStop ); 179 | } 180 | 181 | /** 182 | * {@inheritDoc} 183 | */ 184 | @Override 185 | public void set( int position ) 186 | { 187 | m_tabStops.add( Integer.valueOf( position ) ); 188 | } 189 | } 190 | 191 | // CONSTANTS 192 | 193 | /** 194 | * The font name of Java's mono-spaced font. 195 | */ 196 | protected static final String FONT_MONOSPACED = "Monospaced"; 197 | 198 | private static final int OPTION_ORIGIN = 0; 199 | private static final int OPTION_REVERSE = 1; 200 | private static final int OPTION_AUTOWRAP = 2; 201 | private static final int OPTION_NEWLINE = 3; 202 | private static final int OPTION_INSERT = 4; 203 | 204 | // VARIABLES 205 | 206 | private final CursorImpl m_cursor; 207 | private final ITabulator m_tabulator; 208 | private final IKeyMapper m_keymapper; 209 | 210 | protected final BitSet m_options; 211 | protected final TextAttributes m_textAttributes; 212 | 213 | private volatile ITerminalFrontend m_frontend; 214 | private volatile BitSet m_heatMap; 215 | private volatile TextCell[] m_buffer; 216 | private volatile int m_width; 217 | private volatile int m_height; 218 | 219 | private int m_logLevel; 220 | 221 | private int m_firstScrollLine; 222 | private int m_lastScrollLine; 223 | /** 224 | * denotes that the last written character caused a wrap to the next line (if 225 | * AutoWrap is enabled). 226 | */ 227 | private boolean m_wrapped; 228 | 229 | // CONSTRUCTORS 230 | 231 | /** 232 | * Creates a new {@link AbstractTerminal} instance. 233 | * 234 | * @param columns 235 | * the initial number of columns in this terminal, > 0; 236 | * @param lines 237 | * the initial number of lines in this terminal, > 0. 238 | */ 239 | protected AbstractTerminal( final int columns, final int lines ) 240 | { 241 | m_keymapper = createKeyMapper(); 242 | m_textAttributes = new TextAttributes(); 243 | m_cursor = new CursorImpl(); 244 | m_options = new BitSet(); 245 | m_tabulator = new DefaultTabulator( columns ); 246 | 247 | internalSetDimensions( columns, lines ); 248 | 249 | m_logLevel = 0; 250 | } 251 | 252 | // METHODS 253 | 254 | /** 255 | * Clears the current line. 256 | * 257 | * @param mode 258 | * the clear modus: 0 = erase from cursor to right (default), 1 = 259 | * erase from cursor to left, 2 = erase entire line. 260 | */ 261 | public void clearLine( final int mode ) 262 | { 263 | final int xPos = m_cursor.getX(); 264 | final int yPos = m_cursor.getY(); 265 | 266 | int idx; 267 | 268 | switch ( mode ) 269 | { 270 | case 0: 271 | // erase from cursor to end of line... 272 | idx = getAbsoluteIndex( xPos, yPos ); 273 | break; 274 | case 1: 275 | // erase from cursor to start of line... 276 | idx = getAbsoluteIndex( xPos, yPos ); 277 | break; 278 | case 2: 279 | // erase entire line... 280 | idx = getAbsoluteIndex( 0, yPos ); 281 | break; 282 | 283 | default: 284 | throw new IllegalArgumentException( "Invalid clear line mode!" ); 285 | } 286 | 287 | clearLine( mode, idx, false /* aKeepProtectedCells */); 288 | } 289 | 290 | /** 291 | * Clears the screen. 292 | * 293 | * @param mode 294 | * the clear modus: 0 = erase from cursor to below (default), 1 = 295 | * erase from cursor to top, 2 = erase entire screen. 296 | */ 297 | public void clearScreen( final int mode ) 298 | { 299 | final int xPos = m_cursor.getX(); 300 | final int yPos = m_cursor.getY(); 301 | 302 | clearScreen( mode, getAbsoluteIndex( xPos, yPos ), false /* aKeepProtectedCells */); 303 | } 304 | 305 | /** 306 | * {@inheritDoc} 307 | */ 308 | @Override 309 | public void close() throws IOException 310 | { 311 | m_buffer = null; 312 | m_heatMap = null; 313 | } 314 | 315 | /** 316 | * {@inheritDoc} 317 | */ 318 | @Override 319 | public ICursor getCursor() 320 | { 321 | return m_cursor; 322 | } 323 | 324 | /** 325 | * Returns the first line that can be scrolled, by default line 0. 326 | * 327 | * @return the index of the first scroll line, >= 0. 328 | */ 329 | public final int getFirstScrollLine() 330 | { 331 | if ( !isOriginMode() ) 332 | { 333 | // If not relative to the scroll-region origin, then we simply return 334 | // 0... 335 | return 0; 336 | } 337 | return m_firstScrollLine; 338 | } 339 | 340 | /** 341 | * {@inheritDoc} 342 | */ 343 | @Override 344 | public final ITerminalFrontend getFrontend() 345 | { 346 | return m_frontend; 347 | } 348 | 349 | /** 350 | * {@inheritDoc} 351 | */ 352 | @Override 353 | public int getHeight() 354 | { 355 | return m_height; 356 | } 357 | 358 | /** 359 | * {@inheritDoc} 360 | */ 361 | @Override 362 | public final IKeyMapper getKeyMapper() 363 | { 364 | return m_keymapper; 365 | } 366 | 367 | /** 368 | * Returns the last line that can be scrolled, by default the screen height 369 | * minus 1. 370 | * 371 | * @return the index of the last scroll line, >= 0 && < {@link #getHeight()} . 372 | */ 373 | public final int getLastScrollLine() 374 | { 375 | if ( !isOriginMode() ) 376 | { 377 | // If not relative to the scroll-region origin, then we simply return 378 | // the height of the screen minus one... 379 | return getHeight() - 1; 380 | } 381 | return m_lastScrollLine; 382 | } 383 | 384 | /** 385 | * {@inheritDoc} 386 | */ 387 | @Override 388 | public ITabulator getTabulator() 389 | { 390 | return m_tabulator; 391 | } 392 | 393 | /** 394 | * {@inheritDoc} 395 | */ 396 | @Override 397 | public int getWidth() 398 | { 399 | return m_width; 400 | } 401 | 402 | /** 403 | * Returns whether or not the auto-newline mode is enabled. 404 | *

405 | * When auto-newline mode is enabled, a LF, FF or VT will cause the cursor to 406 | * move to the first column of the next line. When disabled, it will 407 | * stay in the same column while moving the cursor to the next line. 408 | *

409 | * 410 | * @return true if auto-wrap mode is enabled, false 411 | * otherwise. 412 | * @see #setAutoNewlineMode(boolean) 413 | */ 414 | public final boolean isAutoNewlineMode() 415 | { 416 | return m_options.get( OPTION_NEWLINE ); 417 | } 418 | 419 | /** 420 | * Returns whether or not the auto-wrap mode is enabled. 421 | * 422 | * @return true if auto-wrap mode is enabled, false 423 | * otherwise. 424 | * @see #setAutoWrap(boolean) 425 | */ 426 | public final boolean isAutoWrapMode() 427 | { 428 | return m_options.get( OPTION_AUTOWRAP ); 429 | } 430 | 431 | /** 432 | * Returns whether or not insert mode is enabled. 433 | * 434 | * @return true if insert mode is enabled, false 435 | * otherwise. 436 | * @see #setInsertMode(boolean) 437 | */ 438 | public final boolean isInsertMode() 439 | { 440 | return m_options.get( OPTION_INSERT ); 441 | } 442 | 443 | /** 444 | * Returns whether or not the origin mode is enabled. 445 | * 446 | * @return true if origin mode is enabled, false 447 | * otherwise. 448 | * @see #setOriginMode(boolean) 449 | */ 450 | public final boolean isOriginMode() 451 | { 452 | return m_options.get( OPTION_ORIGIN ); 453 | } 454 | 455 | /** 456 | * Returns whether or not the reverse mode is enabled. 457 | * 458 | * @return true if reverse mode is enabled, false 459 | * otherwise. 460 | * @see #setReverse(boolean) 461 | */ 462 | public final boolean isReverseMode() 463 | { 464 | return m_options.get( OPTION_REVERSE ); 465 | } 466 | 467 | /** 468 | * Moves the cursor to the given X,Y position. 469 | * 470 | * @param x 471 | * the absolute X-position, zero-based (zero meaning start of current 472 | * line). If -1, then the current X-position is unchanged. 473 | * @param y 474 | * the absolute Y-position, zero-based (zero meaning start of current 475 | * screen). If -1, then the current Y-position is unchanged. 476 | */ 477 | public void moveCursorAbsolute( final int x, final int y ) 478 | { 479 | int xPos = x; 480 | if ( xPos < 0 ) 481 | { 482 | xPos = m_cursor.getX(); 483 | } 484 | if ( xPos >= m_width ) 485 | { 486 | xPos = m_width; 487 | } 488 | 489 | int yPos = y; 490 | if ( yPos < 0 ) 491 | { 492 | yPos = m_cursor.getY(); 493 | } 494 | if ( yPos >= m_height ) 495 | { 496 | yPos = m_height - 1; 497 | } 498 | 499 | m_cursor.setPosition( xPos, yPos ); 500 | } 501 | 502 | /** 503 | * Moves the cursor relatively to the given X,Y position. 504 | * 505 | * @param x 506 | * the relative X-position to move. If > 0, then move to the right; 507 | * if 0, then the X-position is unchanged; if < 0, then move to the 508 | * left; 509 | * @param y 510 | * the relative Y-position to move. If > 0, then move to the bottom; 511 | * if 0, then the Y-position is unchanged; if < 0, then move to the 512 | * top. 513 | */ 514 | public void moveCursorRelative( final int x, final int y ) 515 | { 516 | int xPos = Math.max( 0, m_cursor.getX() + x ); 517 | int yPos = Math.max( 0, m_cursor.getY() + y ); 518 | 519 | moveCursorAbsolute( xPos, yPos ); 520 | } 521 | 522 | /** 523 | * {@inheritDoc} 524 | */ 525 | @Override 526 | public final int read( CharSequence chars ) throws IOException 527 | { 528 | int r = doReadInput( chars ); 529 | 530 | if ( m_frontend != null && m_frontend.isListening() ) 531 | { 532 | TextCell[] b = m_buffer.clone(); 533 | BitSet hm = ( BitSet )m_heatMap.clone(); 534 | 535 | m_frontend.terminalChanged( b, hm ); 536 | 537 | // Mark all "hot spots" as being processed... 538 | m_heatMap.clear(); 539 | } 540 | 541 | return r; 542 | } 543 | 544 | /** 545 | * {@inheritDoc} 546 | */ 547 | @Override 548 | public void reset() 549 | { 550 | // Clear the entire screen... 551 | clearScreen( 2 ); 552 | // Move cursor to the first (upper left) position... 553 | updateCursorByAbsoluteIndex( getFirstAbsoluteIndex() ); 554 | // Reset scroll region... 555 | m_firstScrollLine = 0; 556 | m_lastScrollLine = getHeight() - 1; 557 | } 558 | 559 | /** 560 | * Scrolls a given number of lines down, inserting empty lines at the top of 561 | * the scrolling region. The contents of the lines scrolled off the screen are 562 | * lost. 563 | * 564 | * @param lines 565 | * the number of lines to scroll down, > 0. 566 | * @see #setScrollRegion(int, int) 567 | */ 568 | public void scrollDown( final int lines ) 569 | { 570 | scrollDown( m_firstScrollLine, m_lastScrollLine, lines ); 571 | } 572 | 573 | /** 574 | * Scrolls a given number of lines up, inserting empty lines at the bottom of 575 | * the scrolling region. The contents of the lines scrolled off the screen are 576 | * lost. 577 | * 578 | * @param lines 579 | * the number of lines to scroll up, > 0. 580 | * @see #setScrollRegion(int, int) 581 | */ 582 | public void scrollUp( final int lines ) 583 | { 584 | scrollUp( m_firstScrollLine, m_lastScrollLine, lines ); 585 | } 586 | 587 | /** 588 | * Enables or disables the auto-newline mode. 589 | * 590 | * @param enable 591 | * true to enable auto-newline mode, false 592 | * to disable it. 593 | */ 594 | public void setAutoNewlineMode( boolean enable ) 595 | { 596 | m_options.set( OPTION_NEWLINE, enable ); 597 | } 598 | 599 | /** 600 | * Enables or disables the auto-wrap mode. 601 | * 602 | * @param enable 603 | * true to enable auto-wrap mode, false to 604 | * disable it. 605 | */ 606 | public void setAutoWrap( boolean enable ) 607 | { 608 | m_options.set( OPTION_AUTOWRAP, enable ); 609 | } 610 | 611 | /** 612 | * Sets the dimensions of this terminal to the given width and height. 613 | * 614 | * @param newWidth 615 | * the new width of this terminal, in columns. If <= 0, then the 616 | * current width will be used; 617 | * @param newHeight 618 | * the new height of this terminal, in lines. If <= 0, then the 619 | * current height will be used. 620 | */ 621 | public void setDimensions( int newWidth, int newHeight ) 622 | { 623 | if ( newWidth <= 0 ) 624 | { 625 | newWidth = m_width; 626 | } 627 | if ( newHeight <= 0 ) 628 | { 629 | newHeight = m_height; 630 | } 631 | 632 | if ( ( newWidth == m_width ) && ( newHeight == m_height ) ) 633 | { 634 | // Nothing to do... 635 | return; 636 | } 637 | 638 | internalSetDimensions( newWidth, newHeight ); 639 | } 640 | 641 | /** 642 | * {@inheritDoc} 643 | */ 644 | @Override 645 | public void setFrontend( ITerminalFrontend frontend ) 646 | { 647 | if ( frontend == null ) 648 | { 649 | throw new IllegalArgumentException( "Frontend cannot be null!" ); 650 | } 651 | m_frontend = frontend; 652 | } 653 | 654 | /** 655 | * Enables or disables the insert mode. 656 | * 657 | * @param enable 658 | * true to enable insert mode, false to 659 | * disable it. 660 | */ 661 | public void setInsertMode( boolean enable ) 662 | { 663 | m_options.set( OPTION_INSERT, enable ); 664 | } 665 | 666 | /** 667 | * @param logLevel 668 | * the logLevel to set 669 | */ 670 | public void setLogLevel( int logLevel ) 671 | { 672 | m_logLevel = logLevel; 673 | } 674 | 675 | /** 676 | * Enables or disables the origin mode. 677 | *

678 | * When the origin mode is set, cursor addressing is relative to the upper 679 | * left corner of the scrolling region. 680 | *

681 | * 682 | * @param enable 683 | * true to enable origin mode, false to 684 | * disable it. 685 | * @see #setScrollRegion(int, int) 686 | */ 687 | public void setOriginMode( boolean enable ) 688 | { 689 | m_options.set( OPTION_ORIGIN, enable ); 690 | } 691 | 692 | /** 693 | * Enables or disables the reverse mode. 694 | * 695 | * @param enable 696 | * true to enable reverse mode, false to 697 | * disable it. 698 | */ 699 | public void setReverse( boolean enable ) 700 | { 701 | m_options.set( OPTION_REVERSE, enable ); 702 | // The entire screen should be redrawn... 703 | m_heatMap.set( getFirstAbsoluteIndex(), getLastAbsoluteIndex() ); 704 | 705 | if ( m_frontend != null ) 706 | { 707 | m_frontend.setReverse( enable ); 708 | } 709 | } 710 | 711 | /** 712 | * Sets the scrolling region. By default the entire screen is set as scrolling 713 | * region. 714 | * 715 | * @param topIndex 716 | * the top line that will be scrolled, >= 0; 717 | * @param bottomIndex 718 | * the bottom line that will be scrolled, >= aTopIndex. 719 | */ 720 | public void setScrollRegion( final int topIndex, final int bottomIndex ) 721 | { 722 | if ( topIndex < 0 ) 723 | { 724 | throw new IllegalArgumentException( "TopIndex cannot be negative!" ); 725 | } 726 | if ( bottomIndex <= topIndex ) 727 | { 728 | throw new IllegalArgumentException( "BottomIndex cannot be equal or less than TopIndex!" ); 729 | } 730 | 731 | m_firstScrollLine = Math.max( 0, topIndex ); 732 | m_lastScrollLine = Math.min( getHeight() - 1, bottomIndex ); 733 | } 734 | 735 | /** 736 | * {@inheritDoc} 737 | */ 738 | @Override 739 | public String toString() 740 | { 741 | StringBuilder sb = new StringBuilder(); 742 | for ( int idx = getFirstAbsoluteIndex(); idx <= getLastAbsoluteIndex(); idx++ ) 743 | { 744 | ITextCell cell = getCellAt( idx ); 745 | if ( ( idx > 0 ) && ( idx % getWidth() ) == 0 ) 746 | { 747 | sb.append( "\n" ); 748 | } 749 | sb.append( cell == null ? ' ' : cell.getChar() ); 750 | } 751 | return sb.toString(); 752 | } 753 | 754 | /** 755 | * {@inheritDoc} 756 | */ 757 | @Override 758 | public int write( CharSequence response ) throws IOException 759 | { 760 | int length = 0; 761 | 762 | final Writer w = getWriter(); 763 | if ( ( response != null ) && ( w != null ) ) 764 | { 765 | w.write( response.toString() ); 766 | w.flush(); 767 | 768 | length = response.length(); 769 | } 770 | 771 | return length; 772 | } 773 | 774 | /** 775 | * Clears all tab stops. 776 | */ 777 | protected final void clearAllTabStops() 778 | { 779 | getTabulator().clearAll(); 780 | } 781 | 782 | /** 783 | * Clears the line using the given absolute index as cursor position. 784 | * 785 | * @param mode 786 | * the clear modus: 0 = erase from cursor to right (default), 1 = 787 | * erase from cursor to left, 2 = erase entire line. 788 | * @param absoluteIndex 789 | * the absolute index of the cursor; 790 | * @param keepProtectedCells 791 | * true to honor the 'protected' option in text cells 792 | * leaving those cells as-is, false to disregard this 793 | * option and clear all text cells. 794 | */ 795 | protected final void clearLine( final int mode, final int absoluteIndex, final boolean keepProtectedCells ) 796 | { 797 | int width = getWidth(); 798 | int yPos = ( int )Math.floor( absoluteIndex / width ); 799 | int xPos = absoluteIndex - ( yPos * width ); 800 | 801 | int idx; 802 | int length; 803 | 804 | switch ( mode ) 805 | { 806 | case 0: 807 | // erase from cursor to end of line... 808 | idx = absoluteIndex; 809 | length = width - xPos; 810 | break; 811 | case 1: 812 | // erase from cursor to start of line... 813 | idx = absoluteIndex - xPos; 814 | length = xPos + 1; 815 | break; 816 | case 2: 817 | // erase entire line... 818 | idx = absoluteIndex - xPos; 819 | length = width; 820 | break; 821 | 822 | default: 823 | throw new IllegalArgumentException( "Invalid clear line mode!" ); 824 | } 825 | 826 | for ( int i = 0; i < length; i++ ) 827 | { 828 | removeChar( idx++, keepProtectedCells ); 829 | } 830 | } 831 | 832 | /** 833 | * Clears the screen using the given absolute index as cursor position. 834 | * 835 | * @param mode 836 | * the clear modus: 0 = erase from cursor to below (default), 1 = 837 | * erase from cursor to top, 2 = erase entire screen. 838 | * @param absoluteIndex 839 | * the absolute index of the cursor; 840 | * @param keepProtectedCells 841 | * true to honor the 'protected' option in text cells 842 | * leaving those cells as-is, false to disregard this 843 | * option and clear all text cells. 844 | */ 845 | protected final void clearScreen( final int mode, final int absoluteIndex, final boolean keepProtectedCells ) 846 | { 847 | switch ( mode ) 848 | { 849 | case 0: 850 | // erase from cursor to end of screen... 851 | int lastIdx = getLastAbsoluteIndex(); 852 | for ( int i = absoluteIndex; i <= lastIdx; i++ ) 853 | { 854 | removeChar( i, keepProtectedCells ); 855 | } 856 | break; 857 | case 1: 858 | // erase from cursor to start of screen... 859 | int firstIdx = getFirstAbsoluteIndex(); 860 | for ( int i = firstIdx; i <= absoluteIndex; i++ ) 861 | { 862 | removeChar( i, keepProtectedCells ); 863 | } 864 | break; 865 | case 2: 866 | // erase entire screen... 867 | if ( keepProtectedCells ) 868 | { 869 | // Be selective in what we remove... 870 | for ( int i = getFirstAbsoluteIndex(), last = getLastAbsoluteIndex(); i <= last; i++ ) 871 | { 872 | removeChar( i, keepProtectedCells ); 873 | } 874 | } 875 | else 876 | { 877 | // Don't be selective in what we remove... 878 | Arrays.fill( m_buffer, getFirstAbsoluteIndex(), getLastAbsoluteIndex() + 1, new TextCell( ' ', 879 | getAttributes() ) ); 880 | // Update the heat map... 881 | m_heatMap.set( getFirstAbsoluteIndex(), getLastAbsoluteIndex() ); 882 | } 883 | break; 884 | 885 | default: 886 | throw new IllegalArgumentException( "Invalid clear screen mode!" ); 887 | } 888 | } 889 | 890 | /** 891 | * Factory method for creating {@link IKeyMapper} instances. 892 | * 893 | * @return a new {@link IKeyMapper} instance, never null. 894 | */ 895 | protected IKeyMapper createKeyMapper() 896 | { 897 | return new DefaultKeyMapper(); 898 | } 899 | 900 | /** 901 | * Deletes a given number of characters at the absolute index, first shifting 902 | * the remaining characters on that line to the left, inserting spaces at the 903 | * end of the line. 904 | * 905 | * @param absoluteIndex 906 | * the absolute index to delete the character at; 907 | * @param count 908 | * the number of times to insert the given character, > 0. 909 | * @return the next index. 910 | */ 911 | protected final int deleteChars( final int absoluteIndex, final int count ) 912 | { 913 | int col = ( absoluteIndex % getWidth() ); 914 | int length = Math.max( 0, getWidth() - col - count ); 915 | 916 | // Make room for the new characters at the end... 917 | System.arraycopy( m_buffer, absoluteIndex + count, m_buffer, absoluteIndex, length ); 918 | 919 | // Fill the created room with the character to insert... 920 | int startIdx = absoluteIndex + length; 921 | int endIdx = absoluteIndex + getWidth() - col; 922 | Arrays.fill( m_buffer, startIdx, endIdx, new TextCell( ' ', getAttributes() ) ); 923 | 924 | // Update the heat map for the *full* line... 925 | m_heatMap.set( absoluteIndex, absoluteIndex + getWidth() - col ); 926 | 927 | return absoluteIndex; 928 | } 929 | 930 | /** 931 | * Provides the actual implementation for {@link #read(CharSequence)}. 932 | * 933 | * @see {@link #read(CharSequence)} 934 | */ 935 | protected abstract int doReadInput( CharSequence chars ) throws IOException; 936 | 937 | /** 938 | * Returns the absolute index according to the current cursor position. 939 | * 940 | * @return an absolute index of the cursor position, >= 0. 941 | */ 942 | protected final int getAbsoluteCursorIndex() 943 | { 944 | return getAbsoluteIndex( m_cursor.getX(), m_cursor.getY() ); 945 | } 946 | 947 | /** 948 | * Returns the absolute index according to the given X,Y-position. 949 | * 950 | * @param x 951 | * the X-position; 952 | * @param y 953 | * the Y-position. 954 | * @return an absolute index of the cursor position, >= 0. 955 | */ 956 | protected final int getAbsoluteIndex( final int x, final int y ) 957 | { 958 | return ( y * getWidth() ) + x; 959 | } 960 | 961 | /** 962 | * @return the (encoded) text attributes. 963 | */ 964 | protected final short getAttributes() 965 | { 966 | return m_textAttributes.getAttributes(); 967 | } 968 | 969 | /** 970 | * Returns the cell at the given absolute index. 971 | * 972 | * @param absoluteIndex 973 | * the absolute of the cell to retrieve. 974 | * @return the text cell at the given index, can be null if no 975 | * cell is defined. 976 | */ 977 | protected final ITextCell getCellAt( final int absoluteIndex ) 978 | { 979 | return m_buffer[absoluteIndex]; 980 | } 981 | 982 | /** 983 | * Returns the cell at the given X,Y-position. 984 | * 985 | * @param x 986 | * the X-position of the cell to retrieve; 987 | * @param y 988 | * the Y-position of the cell to retrieve. 989 | * @return the text cell at the given X,Y-position, or null if 990 | * there is no such cell. 991 | */ 992 | protected final ITextCell getCellAt( final int x, final int y ) 993 | { 994 | return getCellAt( getAbsoluteIndex( x, y ) ); 995 | } 996 | 997 | /** 998 | * @return the first absolute index of this screen, >= 0. 999 | */ 1000 | protected final int getFirstAbsoluteIndex() 1001 | { 1002 | return getAbsoluteIndex( 0, 0 ); 1003 | } 1004 | 1005 | /** 1006 | * @return the last absolute index of this screen, >= 0. 1007 | */ 1008 | protected final int getLastAbsoluteIndex() 1009 | { 1010 | return getAbsoluteIndex( getWidth() - 1, getHeight() - 1 ); 1011 | } 1012 | 1013 | /** 1014 | * Inserts a given character at the absolute index, first shifting the 1015 | * remaining characters on that line to the right (possibly shifting text of 1016 | * the line). 1017 | * 1018 | * @param absoluteIndex 1019 | * the absolute index to insert the character at; 1020 | * @param ch 1021 | * the character to insert; 1022 | * @param count 1023 | * the number of times to insert the given character, > 0. 1024 | * @return the next index. 1025 | */ 1026 | protected final int insertChars( final int absoluteIndex, final char ch, final int count ) 1027 | { 1028 | int col = absoluteIndex % getWidth(); 1029 | int length = getWidth() - col - count; 1030 | 1031 | // Make room for the new characters... 1032 | System.arraycopy( m_buffer, absoluteIndex, m_buffer, absoluteIndex + count, length ); 1033 | 1034 | // Fill the created room with the character to insert... 1035 | Arrays.fill( m_buffer, absoluteIndex, absoluteIndex + count, new TextCell( ch, getAttributes() ) ); 1036 | 1037 | // Update the heat map for the *full* line... 1038 | m_heatMap.set( absoluteIndex, absoluteIndex + getWidth() - col ); 1039 | 1040 | return absoluteIndex; 1041 | } 1042 | 1043 | /** 1044 | * @return true if the last written character caused a wrap to 1045 | * next line, false otherwise. 1046 | */ 1047 | protected final boolean isWrapped() 1048 | { 1049 | return m_wrapped; 1050 | } 1051 | 1052 | /** 1053 | * Logs the given text verbatimely at loglevel 0 or higher. 1054 | * 1055 | * @param text 1056 | * the text to log, cannot be null. 1057 | */ 1058 | protected final void log( String text ) 1059 | { 1060 | if ( m_logLevel < 0 ) 1061 | { 1062 | return; 1063 | } 1064 | 1065 | System.out.printf( "LOG> %s%n", text ); 1066 | } 1067 | 1068 | /** 1069 | * Removes the character at the absolute index. 1070 | * 1071 | * @param absoluteIndex 1072 | * the index on which to remove the character, >= 0; 1073 | * @param keepProtectedCells 1074 | * true to honor the 'protected' bit of text cells and 1075 | * leave the text cell unchanged, false to ignore this 1076 | * bit and clear the text cell anyways. 1077 | * @return the absolute index on which the character was removed. 1078 | */ 1079 | protected final int removeChar( final int absoluteIndex, final boolean keepProtectedCells ) 1080 | { 1081 | int idx = absoluteIndex; 1082 | int firstIdx = getFirstAbsoluteIndex(); 1083 | if ( idx < firstIdx ) 1084 | { 1085 | return firstIdx; 1086 | } 1087 | if ( idx > getLastAbsoluteIndex() ) 1088 | { 1089 | idx = getLastAbsoluteIndex(); 1090 | } 1091 | 1092 | // Clear the character at the given position, using the most current 1093 | // attributes... 1094 | if ( !( keepProtectedCells && m_buffer[idx].isProtected() ) ) 1095 | { 1096 | m_buffer[idx] = new TextCell( ' ', getAttributes() ); 1097 | m_heatMap.set( idx ); 1098 | } 1099 | 1100 | return idx; 1101 | } 1102 | 1103 | /** 1104 | * Resets the wrapped state. 1105 | */ 1106 | protected final void resetWrapped() 1107 | { 1108 | m_wrapped = false; 1109 | } 1110 | 1111 | /** 1112 | * Scrolls all lines between [firstScrollLine, lastScrollLine] the given 1113 | * number of lines down. 1114 | * 1115 | * @param firstScrollLine 1116 | * the first line of the scroll region; 1117 | * @param lastScrollLine 1118 | * the last line of the scroll region; 1119 | * @param lines 1120 | * the number of lines to scroll, > 0. 1121 | */ 1122 | protected void scrollDown( final int firstScrollLine, final int lastScrollLine, final int lines ) 1123 | { 1124 | if ( firstScrollLine < 0 ) 1125 | { 1126 | throw new IllegalArgumentException( "First scroll line cannot be less than zero!" ); 1127 | } 1128 | if ( lastScrollLine >= getHeight() ) 1129 | { 1130 | throw new IllegalArgumentException( "Last scroll line cannot be greater than terminal height!" ); 1131 | } 1132 | if ( lastScrollLine < firstScrollLine ) 1133 | { 1134 | throw new IllegalArgumentException( "Scroll region cannot be negative!" ); 1135 | } 1136 | if ( lines < 1 ) 1137 | { 1138 | throw new IllegalArgumentException( "Invalid number of lines!" ); 1139 | } 1140 | 1141 | int region = ( lastScrollLine - firstScrollLine + 1 ); 1142 | int n = Math.min( lines, region ); 1143 | int width = getWidth(); 1144 | 1145 | int srcPos = firstScrollLine * width; 1146 | int destPos = ( n + firstScrollLine ) * width; 1147 | int length = ( lastScrollLine + 1 ) * width - destPos; 1148 | 1149 | if ( length > 0 ) 1150 | { 1151 | System.arraycopy( m_buffer, srcPos, m_buffer, destPos, length ); 1152 | } 1153 | 1154 | Arrays.fill( m_buffer, srcPos, destPos, new TextCell( ' ', getAttributes() ) ); 1155 | // Update the heat map... 1156 | m_heatMap.set( srcPos, destPos ); 1157 | } 1158 | 1159 | /** 1160 | * Scrolls all lines between [firstScrollLine, lastScrollLine] the given 1161 | * number of lines up. 1162 | * 1163 | * @param firstScrollLine 1164 | * the first line of the scroll region; 1165 | * @param lastScrollLine 1166 | * the last line of the scroll region; 1167 | * @param lines 1168 | * the number of lines to scroll, > 0. 1169 | */ 1170 | protected void scrollUp( final int firstScrollLine, final int lastScrollLine, final int lines ) 1171 | { 1172 | if ( firstScrollLine < 0 ) 1173 | { 1174 | throw new IllegalArgumentException( "First scroll line cannot be less than zero!" ); 1175 | } 1176 | if ( lastScrollLine >= getHeight() ) 1177 | { 1178 | throw new IllegalArgumentException( "Last scroll line cannot be greater than terminal height!" ); 1179 | } 1180 | if ( lastScrollLine < firstScrollLine ) 1181 | { 1182 | throw new IllegalArgumentException( "Scroll region cannot be negative!" ); 1183 | } 1184 | if ( lines < 1 ) 1185 | { 1186 | throw new IllegalArgumentException( "Invalid number of lines!" ); 1187 | } 1188 | 1189 | int region = ( lastScrollLine - firstScrollLine + 1 ); 1190 | int n = Math.min( lines, region ); 1191 | int width = getWidth(); 1192 | 1193 | int srcPos = ( n + firstScrollLine ) * width; 1194 | int destPos = firstScrollLine * width; 1195 | int lastPos = ( lastScrollLine + 1 ) * width; 1196 | int length = lastPos - srcPos; 1197 | 1198 | if ( length > 0 ) 1199 | { 1200 | System.arraycopy( m_buffer, srcPos, m_buffer, destPos, length ); 1201 | } 1202 | Arrays.fill( m_buffer, destPos + length, srcPos + length, new TextCell( ' ', getAttributes() ) ); 1203 | // Update the heat map... 1204 | m_heatMap.set( destPos, lastPos ); 1205 | } 1206 | 1207 | /** 1208 | * Updates the cursor according to the given absolute index. 1209 | * 1210 | * @param absoluteIndex 1211 | * the absolute index to convert back to a X,Y-position. 1212 | */ 1213 | protected final void updateCursorByAbsoluteIndex( final int absoluteIndex ) 1214 | { 1215 | int width = getWidth(); 1216 | int yPos = ( int )Math.floor( absoluteIndex / width ); 1217 | int xPos = absoluteIndex - ( yPos * width ); 1218 | m_cursor.setPosition( xPos, yPos ); 1219 | } 1220 | 1221 | /** 1222 | * Writes a given character at the absolute index, scrolling the screen up if 1223 | * beyond the last index is written. 1224 | * 1225 | * @param absoluteIndex 1226 | * the index on which to write the given char, >= 0; 1227 | * @param ch 1228 | * the character to write; 1229 | * @param aAttributes 1230 | * the attributes to use to write the character. 1231 | * @return the absolute index after which the character was written. 1232 | */ 1233 | protected final int writeChar( final int absoluteIndex, final char ch ) 1234 | { 1235 | int idx = absoluteIndex; 1236 | int lastIdx = getAbsoluteIndex( getWidth() - 1, getLastScrollLine() ); 1237 | int width = getWidth(); 1238 | 1239 | if ( idx > lastIdx ) 1240 | { 1241 | idx -= width; 1242 | scrollUp( 1 ); 1243 | } 1244 | 1245 | if ( idx <= lastIdx ) 1246 | { 1247 | m_buffer[idx] = new TextCell( ch, getAttributes() ); 1248 | m_heatMap.set( idx ); 1249 | } 1250 | 1251 | // determine new absolute index... 1252 | boolean lastColumn = ( ( idx % width ) == ( width - 1 ) ); 1253 | m_wrapped = ( isAutoWrapMode() && lastColumn ); 1254 | if ( !( !isAutoWrapMode() && lastColumn ) ) 1255 | { 1256 | idx++; 1257 | } 1258 | 1259 | return idx; 1260 | } 1261 | 1262 | /** 1263 | * @return the {@link Writer} to write the responses from this terminal to, 1264 | * can be null. 1265 | */ 1266 | private Writer getWriter() 1267 | { 1268 | Writer result = null; 1269 | if ( m_frontend != null ) 1270 | { 1271 | result = m_frontend.getWriter(); 1272 | } 1273 | return result; 1274 | } 1275 | 1276 | /** 1277 | * Sets the dimensions of this terminal to the given width and height. 1278 | * 1279 | * @param width 1280 | * the new width in columns, > 0; 1281 | * @param height 1282 | * the new height in lines, > 0. 1283 | */ 1284 | private void internalSetDimensions( final int width, final int height ) 1285 | { 1286 | TextCell[] newBuffer = new TextCell[width * height]; 1287 | Arrays.fill( newBuffer, new TextCell() ); 1288 | if ( m_buffer != null ) 1289 | { 1290 | int oldWidth = m_width; 1291 | 1292 | for ( int oldIdx = 0; oldIdx < m_buffer.length; oldIdx++ ) 1293 | { 1294 | int oldColumn = ( oldIdx % oldWidth ); 1295 | int oldLine = ( oldIdx / oldWidth ); 1296 | if ( ( oldColumn >= width ) || ( oldLine >= height ) ) 1297 | { 1298 | continue; 1299 | } 1300 | 1301 | int newIdx = ( oldLine * width ) + oldColumn; 1302 | newBuffer[newIdx] = m_buffer[oldIdx]; 1303 | } 1304 | } 1305 | 1306 | m_width = width; 1307 | m_height = height; 1308 | 1309 | m_firstScrollLine = 0; 1310 | m_lastScrollLine = height - 1; 1311 | 1312 | m_buffer = newBuffer; 1313 | m_heatMap = new BitSet( newBuffer.length ); 1314 | 1315 | if ( m_frontend != null ) 1316 | { 1317 | // Notify the frontend that we've changed... 1318 | m_frontend.terminalSizeChanged( width, height ); 1319 | } 1320 | } 1321 | } 1322 | -------------------------------------------------------------------------------- /src/nl/lxtreme/jvt220/terminal/vt220/CharacterSets.java: -------------------------------------------------------------------------------- 1 | /** 2 | * jVT220 - Java VT220 terminal emulator. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | package nl.lxtreme.jvt220.terminal.vt220; 22 | 23 | 24 | /** 25 | * Provides the (graphical) character sets. 26 | */ 27 | public final class CharacterSets 28 | { 29 | // INNER TYPES 30 | 31 | /** 32 | * Provides an enum with names for the supported character sets. 33 | */ 34 | static enum CharacterSet 35 | { 36 | // CONSTANTS 37 | 38 | ASCII( 'B' ) 39 | { 40 | @Override 41 | public int map( int index ) 42 | { 43 | return -1; 44 | } 45 | }, 46 | BRITISH( 'A' ) 47 | { 48 | @Override 49 | public int map( int index ) 50 | { 51 | if ( index == 3 ) 52 | { 53 | // Pound sign... 54 | return '\u00a3'; 55 | } 56 | return -1; 57 | } 58 | }, 59 | DANISH( 'E', '6' ) 60 | { 61 | @Override 62 | public int map( int index ) 63 | { 64 | switch ( index ) 65 | { 66 | case 32: 67 | return '\u00c4'; 68 | case 59: 69 | return '\u00c6'; 70 | case 60: 71 | return '\u00d8'; 72 | case 61: 73 | return '\u00c5'; 74 | case 62: 75 | return '\u00dc'; 76 | case 64: 77 | return '\u00e4'; 78 | case 91: 79 | return '\u00e6'; 80 | case 92: 81 | return '\u00f8'; 82 | case 93: 83 | return '\u00e5'; 84 | case 94: 85 | return '\u00fc'; 86 | default: 87 | return -1; 88 | } 89 | } 90 | }, 91 | DEC_SPECIAL_GRAPHICS( '0', '2' ) 92 | { 93 | @Override 94 | public int map( int index ) 95 | { 96 | if ( index >= 64 && index < 96 ) 97 | { 98 | return ( ( Character )DEC_SPECIAL_CHARS[index - 64][0] ).charValue(); 99 | } 100 | return -1; 101 | } 102 | }, 103 | DEC_SUPPLEMENTAL( 'U', '<' ) 104 | { 105 | @Override 106 | public int map( int index ) 107 | { 108 | if ( index >= 0 && index < 64 ) 109 | { 110 | // Set the 8th bit... 111 | return index + 160; 112 | } 113 | return -1; 114 | } 115 | }, 116 | DUTCH( '4' ) 117 | { 118 | @Override 119 | public int map( int index ) 120 | { 121 | switch ( index ) 122 | { 123 | case 3: 124 | return '\u00a3'; 125 | case 32: 126 | return '\u00be'; 127 | case 59: 128 | return '\u0133'; 129 | case 60: 130 | return '\u00bd'; 131 | case 61: 132 | return '|'; 133 | case 91: 134 | return '\u00a8'; 135 | case 92: 136 | return '\u0192'; 137 | case 93: 138 | return '\u00bc'; 139 | case 94: 140 | return '\u00b4'; 141 | default: 142 | return -1; 143 | } 144 | } 145 | }, 146 | FINNISH( 'C', '5' ) 147 | { 148 | @Override 149 | public int map( int index ) 150 | { 151 | switch ( index ) 152 | { 153 | case 59: 154 | return '\u00c4'; 155 | case 60: 156 | return '\u00d4'; 157 | case 61: 158 | return '\u00c5'; 159 | case 62: 160 | return '\u00dc'; 161 | case 64: 162 | return '\u00e9'; 163 | case 91: 164 | return '\u00e4'; 165 | case 92: 166 | return '\u00f6'; 167 | case 93: 168 | return '\u00e5'; 169 | case 94: 170 | return '\u00fc'; 171 | default: 172 | return -1; 173 | } 174 | } 175 | }, 176 | FRENCH( 'R' ) 177 | { 178 | @Override 179 | public int map( int index ) 180 | { 181 | switch ( index ) 182 | { 183 | case 3: 184 | return '\u00a3'; 185 | case 32: 186 | return '\u00e0'; 187 | case 59: 188 | return '\u00b0'; 189 | case 60: 190 | return '\u00e7'; 191 | case 61: 192 | return '\u00a6'; 193 | case 91: 194 | return '\u00e9'; 195 | case 92: 196 | return '\u00f9'; 197 | case 93: 198 | return '\u00e8'; 199 | case 94: 200 | return '\u00a8'; 201 | default: 202 | return -1; 203 | } 204 | } 205 | }, 206 | FRENCH_CANADIAN( 'Q' ) 207 | { 208 | @Override 209 | public int map( int index ) 210 | { 211 | switch ( index ) 212 | { 213 | case 32: 214 | return '\u00e0'; 215 | case 59: 216 | return '\u00e2'; 217 | case 60: 218 | return '\u00e7'; 219 | case 61: 220 | return '\u00ea'; 221 | case 62: 222 | return '\u00ee'; 223 | case 91: 224 | return '\u00e9'; 225 | case 92: 226 | return '\u00f9'; 227 | case 93: 228 | return '\u00e8'; 229 | case 94: 230 | return '\u00fb'; 231 | default: 232 | return -1; 233 | } 234 | } 235 | }, 236 | GERMAN( 'K' ) 237 | { 238 | @Override 239 | public int map( int index ) 240 | { 241 | switch ( index ) 242 | { 243 | case 32: 244 | return '\u00a7'; 245 | case 59: 246 | return '\u00c4'; 247 | case 60: 248 | return '\u00d6'; 249 | case 61: 250 | return '\u00dc'; 251 | case 91: 252 | return '\u00e4'; 253 | case 92: 254 | return '\u00f6'; 255 | case 93: 256 | return '\u00fc'; 257 | case 94: 258 | return '\u00df'; 259 | default: 260 | return -1; 261 | } 262 | } 263 | }, 264 | ITALIAN( 'Y' ) 265 | { 266 | @Override 267 | public int map( int index ) 268 | { 269 | switch ( index ) 270 | { 271 | case 3: 272 | return '\u00a3'; 273 | case 32: 274 | return '\u00a7'; 275 | case 59: 276 | return '\u00ba'; 277 | case 60: 278 | return '\u00e7'; 279 | case 61: 280 | return '\u00e9'; 281 | case 91: 282 | return '\u00e0'; 283 | case 92: 284 | return '\u00f2'; 285 | case 93: 286 | return '\u00e8'; 287 | case 94: 288 | return '\u00ec'; 289 | default: 290 | return -1; 291 | } 292 | } 293 | }, 294 | SPANISH( 'Z' ) 295 | { 296 | @Override 297 | public int map( int index ) 298 | { 299 | switch ( index ) 300 | { 301 | case 3: 302 | return '\u00a3'; 303 | case 32: 304 | return '\u00a7'; 305 | case 59: 306 | return '\u00a1'; 307 | case 60: 308 | return '\u00d1'; 309 | case 61: 310 | return '\u00bf'; 311 | case 91: 312 | return '\u00b0'; 313 | case 92: 314 | return '\u00f1'; 315 | case 93: 316 | return '\u00e7'; 317 | default: 318 | return -1; 319 | } 320 | } 321 | }, 322 | SWEDISH( 'H', '7' ) 323 | { 324 | @Override 325 | public int map( int index ) 326 | { 327 | switch ( index ) 328 | { 329 | case 32: 330 | return '\u00c9'; 331 | case 59: 332 | return '\u00c4'; 333 | case 60: 334 | return '\u00d6'; 335 | case 61: 336 | return '\u00c5'; 337 | case 62: 338 | return '\u00dc'; 339 | case 64: 340 | return '\u00e9'; 341 | case 91: 342 | return '\u00e4'; 343 | case 92: 344 | return '\u00f6'; 345 | case 93: 346 | return '\u00e5'; 347 | case 94: 348 | return '\u00fc'; 349 | default: 350 | return -1; 351 | } 352 | } 353 | }, 354 | SWISS( '=' ) 355 | { 356 | @Override 357 | public int map( int index ) 358 | { 359 | switch ( index ) 360 | { 361 | case 3: 362 | return '\u00f9'; 363 | case 32: 364 | return '\u00e0'; 365 | case 59: 366 | return '\u00e9'; 367 | case 60: 368 | return '\u00e7'; 369 | case 61: 370 | return '\u00ea'; 371 | case 62: 372 | return '\u00ee'; 373 | case 63: 374 | return '\u00e8'; 375 | case 64: 376 | return '\u00f4'; 377 | case 91: 378 | return '\u00e4'; 379 | case 92: 380 | return '\u00f6'; 381 | case 93: 382 | return '\u00fc'; 383 | case 94: 384 | return '\u00fb'; 385 | default: 386 | return -1; 387 | } 388 | } 389 | }; 390 | 391 | // VARIABLES 392 | 393 | private final int[] m_designations; 394 | 395 | // CONSTRUCTORS 396 | 397 | /** 398 | * Creates a new {@link CharacterSet} instance. 399 | * 400 | * @param designations 401 | * the characters that designate this character set, cannot be 402 | * null. 403 | */ 404 | private CharacterSet( int... designations ) 405 | { 406 | m_designations = designations; 407 | } 408 | 409 | // METHODS 410 | 411 | /** 412 | * Returns the {@link CharacterSet} for the given character. 413 | * 414 | * @param designation 415 | * the character to translate to a {@link CharacterSet}. 416 | * @return a character set name corresponding to the given character, 417 | * defaulting to ASCII if no mapping could be made. 418 | */ 419 | public static CharacterSet valueOf( char designation ) 420 | { 421 | for ( CharacterSet csn : values() ) 422 | { 423 | if ( csn.isDesignation( designation ) ) 424 | { 425 | return csn; 426 | } 427 | } 428 | return ASCII; 429 | } 430 | 431 | /** 432 | * Maps the character with the given index to a character in this character 433 | * set. 434 | * 435 | * @param index 436 | * the index of the character set, >= 0 && < 128. 437 | * @return a mapped character, or -1 if no mapping could be made and the 438 | * ASCII value should be used. 439 | */ 440 | public abstract int map( int index ); 441 | 442 | /** 443 | * Returns whether or not the given designation character belongs to this 444 | * character set's set of designations. 445 | * 446 | * @param designation 447 | * the designation to test for. 448 | * @return true if the given designation character maps to this 449 | * character set, false otherwise. 450 | */ 451 | private boolean isDesignation( char designation ) 452 | { 453 | for ( int i = 0; i < m_designations.length; i++ ) 454 | { 455 | if ( m_designations[i] == designation ) 456 | { 457 | return true; 458 | } 459 | } 460 | return false; 461 | } 462 | } 463 | 464 | /** 465 | * Denotes how a graphic set is designated. 466 | */ 467 | static class GraphicSet 468 | { 469 | // VARIABLES 470 | 471 | private final int m_index; // 0..3 472 | private CharacterSet m_designation; 473 | 474 | // CONSTRUCTORS 475 | 476 | /** 477 | * Creates a new {@link GraphicSet} instance. 478 | */ 479 | public GraphicSet( int index ) 480 | { 481 | if ( index < 0 || index > 3 ) 482 | { 483 | throw new IllegalArgumentException( "Invalid index!" ); 484 | } 485 | m_index = index; 486 | // The default mapping, based on XTerm... 487 | m_designation = CharacterSet.valueOf( ( index == 1 ) ? '0' : 'B' ); 488 | } 489 | 490 | // METHODS 491 | 492 | /** 493 | * @return the designation of this graphic set. 494 | */ 495 | public CharacterSet getDesignation() 496 | { 497 | return m_designation; 498 | } 499 | 500 | /** 501 | * @return the index of this graphics set. 502 | */ 503 | public int getIndex() 504 | { 505 | return m_index; 506 | } 507 | 508 | /** 509 | * Maps a given character index to a concrete character. 510 | * 511 | * @param original 512 | * the original character to map; 513 | * @param index 514 | * the index of the character to map. 515 | * @return the mapped character, or the given original if no mapping could 516 | * be made. 517 | */ 518 | public int map( char original, int index ) 519 | { 520 | int result = m_designation.map( index ); 521 | if ( result < 0 ) 522 | { 523 | // No mapping, simply return the given original one... 524 | result = original; 525 | } 526 | return result; 527 | } 528 | 529 | /** 530 | * Sets the designation of this graphic set. 531 | * 532 | * @param designation 533 | * the designation to set, cannot be null. 534 | * @throws IllegalArgumentException 535 | * in case the given designation was null. 536 | */ 537 | public void setDesignation( CharacterSet designation ) 538 | { 539 | if ( designation == null ) 540 | { 541 | throw new IllegalArgumentException( "Designation cannot be null!" ); 542 | } 543 | m_designation = designation; 544 | } 545 | } 546 | 547 | // CONSTANTS 548 | 549 | private static final int C0_START = 0; 550 | private static final int C0_END = 31; 551 | private static final int C1_START = 128; 552 | private static final int C1_END = 159; 553 | private static final int GL_START = 32; 554 | private static final int GL_END = 127; 555 | private static final int GR_START = 160; 556 | private static final int GR_END = 255; 557 | 558 | public static final String[] ASCII_NAMES = { "", "", "", "", "", "", "", "", 559 | "\b", "\t", "\n", "", "", "\r", "", "", "", "", "", "", "", "", 560 | "", "", "", "", "", "", "", "", "", "", " ", "!", "\"", "#", "$", 561 | "%", "&", "'", "(", ")", "*", "+", ",", "-", ".", "/", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ":", 562 | ";", "<", "=", ">", "?", "@", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", 563 | "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "[", "\\", "]", "^", "_", "`", "a", "b", "c", "d", "e", "f", 564 | "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "{", "|", 565 | "}", "~", "" }; 566 | 567 | /** 568 | * Denotes the mapping for C0 characters. 569 | */ 570 | private static Object C0_CHARS[][] = { { 0, "nul" }, // 571 | { 0, "soh" }, // 572 | { 0, "stx" }, // 573 | { 0, "etx" }, // 574 | { 0, "eot" }, // 575 | { 0, "enq" }, // 576 | { 0, "ack" }, // 577 | { 0, "bel" }, // 578 | { '\b', "bs" }, // 579 | { '\t', "ht" }, // 580 | { '\n', "lf" }, // 581 | { 0, "vt" }, // 582 | { 0, "ff" }, // 583 | { '\r', "cr" }, // 584 | { 0, "so" }, // 585 | { 0, "si" }, // 586 | { 0, "dle" }, // 587 | { 0, "dc1" }, // 588 | { 0, "dc2" }, // 589 | { 0, "dc3" }, // 590 | { 0, "dc4" }, // 591 | { 0, "nak" }, // 592 | { 0, "syn" }, // 593 | { 0, "etb" }, // 594 | { 0, "can" }, // 595 | { 0, "em" }, // 596 | { 0, "sub" }, // 597 | { 0, "esq" }, // 598 | { 0, "fs" }, // 599 | { 0, "gs" }, // 600 | { 0, "rs" }, // 601 | { 0, "us" } }; 602 | 603 | /** 604 | * Denotes the mapping for C1 characters. 605 | */ 606 | private static Object C1_CHARS[][] = { { 0, null }, // 607 | { 0, null }, // 608 | { 0, null }, // 609 | { 0, null }, // 610 | { 0, "ind" }, // 611 | { 0, "nel" }, // 612 | { 0, "ssa" }, // 613 | { 0, "esa" }, // 614 | { 0, "hts" }, // 615 | { 0, "htj" }, // 616 | { 0, "vts" }, // 617 | { 0, "pld" }, // 618 | { 0, "plu" }, // 619 | { 0, "ri" }, // 620 | { 0, "ss2" }, // 621 | { 0, "ss3" }, // 622 | { 0, "dcs" }, // 623 | { 0, "pu1" }, // 624 | { 0, "pu2" }, // 625 | { 0, "sts" }, // 626 | { 0, "cch" }, // 627 | { 0, "mw" }, // 628 | { 0, "spa" }, // 629 | { 0, "epa" }, // 630 | { 0, null }, // 631 | { 0, null }, // 632 | { 0, null }, // 633 | { 0, "csi" }, // 634 | { 0, "st" }, // 635 | { 0, "osc" }, // 636 | { 0, "pm" }, // 637 | { 0, "apc" } }; 638 | 639 | /** 640 | * The DEC special characters (only the last 32 characters). 641 | */ 642 | private static Object DEC_SPECIAL_CHARS[][] = { { '\u25c6', null }, // black_diamond 643 | { '\u2592', null }, // Medium Shade 644 | { '\u2409', null }, // Horizontal tab (HT) 645 | { '\u240c', null }, // Form Feed (FF) 646 | { '\u240d', null }, // Carriage Return (CR) 647 | { '\u240a', null }, // Line Feed (LF) 648 | { '\u00b0', null }, // Degree sign 649 | { '\u00b1', null }, // Plus/minus sign 650 | { '\u2424', null }, // New Line (NL) 651 | { '\u240b', null }, // Vertical Tab (VT) 652 | { '\u2518', null }, // Forms light up and left 653 | { '\u2510', null }, // Forms light down and left 654 | { '\u250c', null }, // Forms light down and right 655 | { '\u2514', null }, // Forms light up and right 656 | { '\u253c', null }, // Forms light vertical and horizontal 657 | { '\u23ba', null }, // Scan 1 658 | { '\u23bb', null }, // Scan 3 659 | { '\u2500', null }, // Scan 5 / Horizontal bar 660 | { '\u23bc', null }, // Scan 7 661 | { '\u23bd', null }, // Scan 9 662 | { '\u251c', null }, // Forms light vertical and right 663 | { '\u2524', null }, // Forms light vertical and left 664 | { '\u2534', null }, // Forms light up and horizontal 665 | { '\u252c', null }, // Forms light down and horizontal 666 | { '\u2502', null }, // vertical bar 667 | { '\u2264', null }, // less than or equal sign 668 | { '\u2265', null }, // greater than or equal sign 669 | { '\u03c0', null }, // pi 670 | { '\u2260', null }, // not equal sign 671 | { '\u00a3', null }, // pound sign 672 | { '\u00b7', null }, // middle dot 673 | { ' ', null }, // 674 | }; 675 | 676 | // CONSTRUCTORS 677 | 678 | /** 679 | * Creates a new {@link CharacterSets} instance, never used. 680 | */ 681 | private CharacterSets() 682 | { 683 | // Nop 684 | } 685 | 686 | // METHODS 687 | 688 | /** 689 | * Returns the character mapping for a given original value using the given 690 | * graphic sets GL and GR. 691 | * 692 | * @param original 693 | * the original character to map; 694 | * @param gl 695 | * the GL graphic set, cannot be null; 696 | * @param gr 697 | * the GR graphic set, cannot be null. 698 | * @return the mapped character. 699 | */ 700 | public static char getChar( char original, GraphicSet gl, GraphicSet gr ) 701 | { 702 | Object[] mapping = getMapping( original, gl, gr ); 703 | 704 | int ch = ( ( Integer )mapping[0] ).intValue(); 705 | if ( ch > 0 ) 706 | { 707 | return ( char )ch; 708 | } 709 | 710 | return ' '; 711 | } 712 | 713 | /** 714 | * Returns the name for the given character using the given graphic sets GL 715 | * and GR. 716 | * 717 | * @param original 718 | * the original character to return the name for; 719 | * @param gl 720 | * the GL graphic set, cannot be null; 721 | * @param gr 722 | * the GR graphic set, cannot be null. 723 | * @return the character name. 724 | */ 725 | public static String getCharName( char original, GraphicSet gl, GraphicSet gr ) 726 | { 727 | Object[] mapping = getMapping( original, gl, gr ); 728 | 729 | String name = ( String )mapping[1]; 730 | if ( name == null ) 731 | { 732 | name = String.format( "<%d>", ( int )original ); 733 | } 734 | 735 | return name; 736 | } 737 | 738 | /** 739 | * Returns the mapping for a given character using the given graphic sets GL 740 | * and GR. 741 | * 742 | * @param original 743 | * the original character to map; 744 | * @param gl 745 | * the GL graphic set, cannot be null; 746 | * @param gr 747 | * the GR graphic set, cannot be null. 748 | * @return the mapped character. 749 | */ 750 | private static Object[] getMapping( char original, GraphicSet gl, GraphicSet gr ) 751 | { 752 | int mappedChar = original; 753 | if ( original >= C0_START && original <= C0_END ) 754 | { 755 | int idx = original - C0_START; 756 | return C0_CHARS[idx]; 757 | } 758 | else if ( original >= C1_START && original <= C1_END ) 759 | { 760 | int idx = original - C1_START; 761 | return C1_CHARS[idx]; 762 | } 763 | else if ( original >= GL_START && original <= GL_END ) 764 | { 765 | int idx = original - GL_START; 766 | mappedChar = gl.map( original, idx ); 767 | } 768 | else if ( original >= GR_START && original <= GR_END ) 769 | { 770 | int idx = original - GR_START; 771 | mappedChar = gr.map( original, idx ); 772 | } 773 | 774 | return new Object[] { mappedChar, null }; 775 | } 776 | } 777 | -------------------------------------------------------------------------------- /src/nl/lxtreme/jvt220/terminal/vt220/CursorImpl.java: -------------------------------------------------------------------------------- 1 | /** 2 | * jVT220 - Java VT220 terminal emulator. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | package nl.lxtreme.jvt220.terminal.vt220; 22 | 23 | 24 | import nl.lxtreme.jvt220.terminal.*; 25 | 26 | 27 | /** 28 | * Provides an implementation of {@link ICursor}. 29 | */ 30 | public class CursorImpl implements ICursor 31 | { 32 | // VARIABLES 33 | 34 | private int m_blinkRate; 35 | private boolean m_visible; 36 | private int m_x; 37 | private int m_y; 38 | 39 | // CONSTRUCTORS 40 | 41 | /** 42 | * Creates a new {@link CursorImpl} instance. 43 | */ 44 | public CursorImpl() 45 | { 46 | m_blinkRate = 500; 47 | m_visible = true; 48 | m_x = 0; 49 | m_y = 0; 50 | } 51 | 52 | // METHODS 53 | 54 | /** 55 | * {@inheritDoc} 56 | */ 57 | @Override 58 | public ICursor clone() 59 | { 60 | try 61 | { 62 | CursorImpl clone = ( CursorImpl )super.clone(); 63 | clone.m_blinkRate = m_blinkRate; 64 | clone.m_visible = m_visible; 65 | clone.m_x = m_x; 66 | clone.m_y = m_y; 67 | return clone; 68 | } 69 | catch ( CloneNotSupportedException e ) 70 | { 71 | throw new RuntimeException( "Cloning not supported!?!" ); 72 | } 73 | } 74 | 75 | /** 76 | * {@inheritDoc} 77 | */ 78 | @Override 79 | public int getBlinkRate() 80 | { 81 | return m_blinkRate; 82 | } 83 | 84 | /** 85 | * {@inheritDoc} 86 | */ 87 | @Override 88 | public int getX() 89 | { 90 | return m_x; 91 | } 92 | 93 | /** 94 | * {@inheritDoc} 95 | */ 96 | @Override 97 | public int getY() 98 | { 99 | return m_y; 100 | } 101 | 102 | /** 103 | * {@inheritDoc} 104 | */ 105 | @Override 106 | public boolean isVisible() 107 | { 108 | return m_visible; 109 | } 110 | 111 | /** 112 | * {@inheritDoc} 113 | */ 114 | @Override 115 | public void setBlinkRate( final int rate ) 116 | { 117 | if ( rate < 0 ) 118 | { 119 | throw new IllegalArgumentException( "Invalid blink rate!" ); 120 | } 121 | m_blinkRate = rate; 122 | } 123 | 124 | /** 125 | * {@inheritDoc} 126 | */ 127 | @Override 128 | public void setVisible( final boolean visible ) 129 | { 130 | m_visible = visible; 131 | } 132 | 133 | /** 134 | * {@inheritDoc} 135 | */ 136 | final void setPosition( final int x, final int y ) 137 | { 138 | m_x = x; 139 | m_y = y; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/nl/lxtreme/jvt220/terminal/vt220/PlainTerminal.java: -------------------------------------------------------------------------------- 1 | /** 2 | * jVT220 - Java VT220 terminal emulator. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | package nl.lxtreme.jvt220.terminal.vt220; 22 | 23 | import static nl.lxtreme.jvt220.terminal.vt220.CharacterSets.*; 24 | 25 | import java.awt.*; 26 | import java.util.concurrent.atomic.*; 27 | 28 | 29 | /** 30 | * Provides a plain terminal, that escapes any non-printable ASCII characters. 31 | */ 32 | public class PlainTerminal extends AbstractTerminal 33 | { 34 | // VARIABLES 35 | 36 | private final AtomicBoolean m_rawMode; 37 | 38 | // CONSTRUCTORS 39 | 40 | /** 41 | * Creates a new {@link PlainTerminal} instance. 42 | * 43 | * @param aColumns 44 | * the initial number of columns in this terminal, > 0; 45 | * @param aLines 46 | * the initial number of lines in this terminal, > 0. 47 | */ 48 | public PlainTerminal( final int aColumns, final int aLines ) 49 | { 50 | super( aColumns, aLines ); 51 | 52 | m_rawMode = new AtomicBoolean(); 53 | } 54 | 55 | // METHODS 56 | 57 | /** 58 | * Returns whether or not this terminal is in "raw" mode. In non-raw mode, the 59 | * non-printable ASCII characters are represented by their name. 60 | * 61 | * @return true if this terminal is in 'raw' mode, 62 | * false (the default) otherwise. 63 | */ 64 | public boolean isRawMode() 65 | { 66 | return m_rawMode.get(); 67 | } 68 | 69 | /** 70 | * Sets whether or not this terminal should display non-printable ASCII 71 | * characters by their name. 72 | * 73 | * @param aRawMode 74 | * false if the names for non-printable ASCII characters 75 | * should be displayed, true otherwise. 76 | */ 77 | public void setRawMode( boolean aRawMode ) 78 | { 79 | boolean old; 80 | do 81 | { 82 | old = m_rawMode.get(); 83 | } 84 | while ( !m_rawMode.compareAndSet( old, aRawMode ) ); 85 | } 86 | 87 | /** 88 | * {@inheritDoc} 89 | */ 90 | @Override 91 | protected int doReadInput( final CharSequence aChars ) 92 | { 93 | if ( aChars == null ) 94 | { 95 | throw new IllegalArgumentException( "Chars cannot be null!" ); 96 | } 97 | 98 | int idx = getAbsoluteCursorIndex(); 99 | 100 | for ( int i = 0; i < aChars.length(); i++ ) 101 | { 102 | char c = aChars.charAt( i ); 103 | 104 | switch ( c ) 105 | { 106 | case '\010': 107 | // Backspace 108 | idx = removeChar( --idx, false /* aKeepProtectedCells */); 109 | break; 110 | 111 | case '\007': 112 | // Bell 113 | Toolkit.getDefaultToolkit().beep(); 114 | break; 115 | 116 | case '\012': 117 | // Newline 118 | idx += getWidth(); 119 | break; 120 | 121 | case '\015': 122 | // Carriage return 123 | idx -= ( idx % getWidth() ); 124 | break; 125 | 126 | default: 127 | if ( !isRawMode() && ( c < ASCII_NAMES.length ) ) 128 | { 129 | String name = ASCII_NAMES[c]; 130 | for ( int j = 0; j < name.length(); j++ ) 131 | { 132 | idx = writeChar( idx, name.charAt( j ) ); 133 | } 134 | } 135 | else 136 | { 137 | idx = writeChar( idx, c ); 138 | } 139 | break; 140 | } 141 | } 142 | 143 | updateCursorByAbsoluteIndex( idx ); 144 | return aChars.length(); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/nl/lxtreme/jvt220/terminal/vt220/TextAttributes.java: -------------------------------------------------------------------------------- 1 | /** 2 | * jVT220 - Java VT220 terminal emulator. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | package nl.lxtreme.jvt220.terminal.vt220; 22 | 23 | 24 | /** 25 | * Denotes a container for text attributes. 26 | */ 27 | class TextAttributes 28 | { 29 | // CONSTANTS 30 | 31 | static final int COLOR_MASK = ( 1 << 5 ) - 1; 32 | static final int BOLD_MASK = 1 << 10; 33 | static final int ITALIC_MASK = 1 << 11; 34 | static final int UNDERLINE_MASK = 1 << 12; 35 | static final int REVERSE_MASK = 1 << 13; 36 | static final int HIDDEN_MASK = 1 << 14; 37 | static final int PROTECTED_MASK = 1 << 15; 38 | 39 | // VARIABLES 40 | 41 | private short m_attr; 42 | 43 | // METHODS 44 | 45 | /** 46 | * {@inheritDoc} 47 | */ 48 | @Override 49 | public boolean equals( Object object ) 50 | { 51 | if ( this == object ) 52 | { 53 | return true; 54 | } 55 | 56 | if ( ( object == null ) || getClass() != object.getClass() ) 57 | { 58 | return false; 59 | } 60 | 61 | final TextAttributes other = ( TextAttributes )object; 62 | return ( m_attr == other.m_attr ); 63 | } 64 | 65 | /** 66 | * Returns the encoded attributes. 67 | * 68 | * @return the encoded attributes as short value. 69 | */ 70 | public short getAttributes() 71 | { 72 | return m_attr; 73 | } 74 | 75 | /** 76 | * {@inheritDoc} 77 | */ 78 | public int getBackground() 79 | { 80 | int bg = ( ( m_attr >> 5 ) & COLOR_MASK ); 81 | return bg; 82 | } 83 | 84 | /** 85 | * {@inheritDoc} 86 | */ 87 | public int getForeground() 88 | { 89 | int fg = ( m_attr & COLOR_MASK ); 90 | return fg; 91 | } 92 | 93 | /** 94 | * {@inheritDoc} 95 | */ 96 | @Override 97 | public int hashCode() 98 | { 99 | final int prime = 31; 100 | int result = 1; 101 | result = prime * result + m_attr; 102 | return result; 103 | } 104 | 105 | /** 106 | * {@inheritDoc} 107 | */ 108 | public boolean isBold() 109 | { 110 | return ( m_attr & BOLD_MASK ) != 0; 111 | } 112 | 113 | /** 114 | * @return 115 | */ 116 | public boolean isHidden() 117 | { 118 | return ( m_attr & HIDDEN_MASK ) != 0; 119 | } 120 | 121 | /** 122 | * {@inheritDoc} 123 | */ 124 | public boolean isItalic() 125 | { 126 | return ( m_attr & ITALIC_MASK ) != 0; 127 | } 128 | 129 | /** 130 | * {@inheritDoc} 131 | */ 132 | public boolean isProtected() 133 | { 134 | return ( m_attr & PROTECTED_MASK ) != 0; 135 | } 136 | 137 | /** 138 | * @return 139 | */ 140 | public boolean isReverse() 141 | { 142 | return ( m_attr & REVERSE_MASK ) != 0; 143 | } 144 | 145 | /** 146 | * {@inheritDoc} 147 | */ 148 | public boolean isUnderline() 149 | { 150 | return ( m_attr & UNDERLINE_MASK ) != 0; 151 | } 152 | 153 | /** 154 | * Resets all attributes to their default values, except for the foreground 155 | * and background color. 156 | */ 157 | public void reset() 158 | { 159 | m_attr &= 0x3FF; // keep lower 10 bits... 160 | } 161 | 162 | /** 163 | * Resets all attributes to their default values. 164 | */ 165 | public void resetAll() 166 | { 167 | m_attr = 0; 168 | } 169 | 170 | /** 171 | * Directly sets the attributes as encoded value. 172 | * 173 | * @param attributes 174 | * the attributes to set. 175 | */ 176 | public void setAttributes( short attributes ) 177 | { 178 | m_attr = attributes; 179 | } 180 | 181 | /** 182 | * Sets the background color. 183 | * 184 | * @param index 185 | * the index of the background color, >= 0 && < 32. 186 | */ 187 | public void setBackground( int index ) 188 | { 189 | int bg = ( index & COLOR_MASK ) << 5; 190 | m_attr &= 0xFE1F; // clear bg color bits... 191 | m_attr |= bg; 192 | } 193 | 194 | /** 195 | * @param enable 196 | * true to enable the bold representation, 197 | * false to disable it. 198 | */ 199 | public void setBold( boolean enable ) 200 | { 201 | setAttrBit( enable, BOLD_MASK ); 202 | } 203 | 204 | /** 205 | * Sets the foreground color. 206 | * 207 | * @param index 208 | * the index of the foreground color, >= 0 && < 32. 209 | */ 210 | public void setForeground( int index ) 211 | { 212 | int fg = index & COLOR_MASK; 213 | m_attr &= 0xFFE0; // clear fg color bits... 214 | m_attr |= fg; 215 | } 216 | 217 | /** 218 | * @param enable 219 | * true to enable the hidden property, 220 | * false to disable it. 221 | */ 222 | public void setHidden( boolean enable ) 223 | { 224 | setAttrBit( enable, HIDDEN_MASK ); 225 | } 226 | 227 | /** 228 | * @param enable 229 | * true to enable the italic property, 230 | * false to disable it. 231 | */ 232 | public void setItalic( boolean enable ) 233 | { 234 | setAttrBit( enable, ITALIC_MASK ); 235 | } 236 | 237 | /** 238 | * @param enable 239 | * true to enable the protected property, 240 | * false to disable it. 241 | */ 242 | public void setProtected( boolean enable ) 243 | { 244 | setAttrBit( enable, PROTECTED_MASK ); 245 | } 246 | 247 | /** 248 | * @param enable 249 | * true to enable the reverse property, 250 | * false to disable it. 251 | */ 252 | public void setReverse( boolean enable ) 253 | { 254 | setAttrBit( enable, REVERSE_MASK ); 255 | } 256 | 257 | /** 258 | * @param enable 259 | * true to enable the underline property, 260 | * false to disable it. 261 | */ 262 | public void setUnderline( boolean enable ) 263 | { 264 | setAttrBit( enable, UNDERLINE_MASK ); 265 | } 266 | 267 | /** 268 | * Sets or resets the bit in the attributes denoted by the given mask. 269 | * 270 | * @param enable 271 | * true to set the bit, false to reset it; 272 | * @param mask 273 | * the mask of the bit to set or reset. 274 | */ 275 | private void setAttrBit( boolean enable, int mask ) 276 | { 277 | if ( enable ) 278 | { 279 | m_attr |= mask; 280 | } 281 | else 282 | { 283 | m_attr &= ~mask; 284 | } 285 | } 286 | } 287 | -------------------------------------------------------------------------------- /src/nl/lxtreme/jvt220/terminal/vt220/TextCell.java: -------------------------------------------------------------------------------- 1 | /** 2 | * jVT220 - Java VT220 terminal emulator. 3 | * 4 | * Licensed to the Apache Software Foundation (ASF) under one 5 | * or more contributor license agreements. See the NOTICE file 6 | * distributed with this work for additional information 7 | * regarding copyright ownership. The ASF licenses this file 8 | * to you under the Apache License, Version 2.0 (the 9 | * "License"); you may not use this file except in compliance 10 | * with the License. You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, 15 | * software distributed under the License is distributed on an 16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | * KIND, either express or implied. See the License for the 18 | * specific language governing permissions and limitations 19 | * under the License. 20 | */ 21 | package nl.lxtreme.jvt220.terminal.vt220; 22 | 23 | 24 | import nl.lxtreme.jvt220.terminal.ITerminal.ITextCell; 25 | 26 | 27 | /** 28 | * Provides an implementation of {@link ITextCell} that packs all its 29 | * information in a character and short value. 30 | */ 31 | final class TextCell extends TextAttributes implements ITextCell 32 | { 33 | // VARIABLES 34 | 35 | private char m_ch; 36 | 37 | // CONSTRUCTORS 38 | 39 | /** 40 | * Creates a new, empty {@link TextCell} instance. 41 | */ 42 | public TextCell() 43 | { 44 | this( ' ', ( short )0 ); 45 | } 46 | 47 | /** 48 | * Creates a new {@link TextCell} instance with the given contents and 49 | * attributes. 50 | * 51 | * @param ch 52 | * the contents of this cell; 53 | * @param attributes 54 | * the attributes of this cell. 55 | */ 56 | public TextCell( char ch, short attributes ) 57 | { 58 | m_ch = ch; 59 | setAttributes( attributes ); 60 | } 61 | 62 | /** 63 | * Creates a new {@link TextCell} instance as copy of the given text cell. 64 | * 65 | * @param cell 66 | * the cell to copy the content + attributes from, cannot be 67 | * null. 68 | */ 69 | public TextCell( TextCell cell ) 70 | { 71 | this( cell.getChar(), cell.getAttributes() ); 72 | } 73 | 74 | // METHODS 75 | 76 | /** 77 | * {@inheritDoc} 78 | */ 79 | @Override 80 | public char getChar() 81 | { 82 | return m_ch; 83 | } 84 | 85 | /** 86 | * {@inheritDoc} 87 | */ 88 | @Override 89 | public String toString() 90 | { 91 | return "[" + m_ch + "]"; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/nl/lxtreme/jvt220/terminal/vt220/packageinfo: -------------------------------------------------------------------------------- 1 | version 1.1.0 2 | -------------------------------------------------------------------------------- /test/nl/lxtreme/jvt220/terminal/swing/CharBufferTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * jVT220 - Java VT220 terminal emulator. 3 | * 4 | * (C) Copyright 2012 - J.W. Janssen, . 5 | */ 6 | package nl.lxtreme.jvt220.terminal.swing; 7 | 8 | 9 | import java.util.concurrent.*; 10 | 11 | import junit.framework.*; 12 | 13 | 14 | /** 15 | * Test cases for {@link CharBuffer}. 16 | */ 17 | public class CharBufferTest extends TestCase 18 | { 19 | // METHODS 20 | 21 | /** 22 | * Tests that calling {@link CharBuffer#append(java.util.List)} with a 23 | * non-null value succeeds. 24 | */ 25 | public void testAppendNotNullOk() 26 | { 27 | CharBuffer charBuffer = new CharBuffer(); 28 | charBuffer.append( 1, 2, 3 ); 29 | 30 | for ( int i = 0; i < charBuffer.length(); i++ ) 31 | { 32 | assertEquals( ( char )( i + 1 ), charBuffer.charAt( i ) ); 33 | } 34 | } 35 | 36 | /** 37 | * Tests that calling {@link CharBuffer#append(java.util.List)} with a null 38 | * value fails. 39 | */ 40 | public void testAppendNullFail() 41 | { 42 | try 43 | { 44 | new CharBuffer().append( ( Integer[] )null ); 45 | fail( "Expected IllegalArgumentException!" ); 46 | } 47 | catch ( IllegalArgumentException e ) 48 | { 49 | // Ok; expected... 50 | } 51 | } 52 | 53 | /** 54 | * Tests that calling {@link CharBuffer#append(java.util.List)} with a 55 | * non-null value succeeds. 56 | */ 57 | public void testAppendWillResizeInternalArrayOk() 58 | { 59 | CharBuffer charBuffer = new CharBuffer(); 60 | for ( int i = 0; i < 100; i++ ) 61 | { 62 | charBuffer.append( i + 1 ); 63 | } 64 | 65 | for ( int i = 0; i < charBuffer.length(); i++ ) 66 | { 67 | assertEquals( ( char )( i + 1 ), charBuffer.charAt( i ) ); 68 | } 69 | } 70 | 71 | /** 72 | * Tests that we cannot call {@link CharBuffer#charAt(int)} on an empty 73 | * buffer. 74 | */ 75 | public void testCharAtEmptyBufferFail() 76 | { 77 | try 78 | { 79 | new CharBuffer().charAt( 0 ); 80 | fail( "Expected IndexOutOfBoundsException!" ); 81 | } 82 | catch ( IndexOutOfBoundsException e ) 83 | { 84 | // Ok; expected... 85 | } 86 | } 87 | 88 | /** 89 | * Tests that we cannot call {@link CharBuffer#charAt(int)} with index two 90 | * succeeds. 91 | */ 92 | public void testCharAtIndexTwoOk() 93 | { 94 | assertEquals( ( char )3, new CharBuffer( 1, 2, 3 ).charAt( 2 ) ); 95 | } 96 | 97 | /** 98 | * Tests that we cannot call {@link CharBuffer#charAt(int)} with index zero 99 | * succeeds. 100 | */ 101 | public void testCharAtIndexZeroOk() 102 | { 103 | assertEquals( ( char )1, new CharBuffer( 1, 2, 3 ).charAt( 0 ) ); 104 | } 105 | 106 | /** 107 | * Tests that we cannot call {@link CharBuffer#charAt(int)} with an index 108 | * beyond the end of the buffer fails. 109 | */ 110 | public void testCharAtWithIndexBeyondLastPositionFail() 111 | { 112 | try 113 | { 114 | new CharBuffer( 1, 2, 3 ).charAt( 3 ); 115 | fail( "Expected IndexOutOfBoundsException!" ); 116 | } 117 | catch ( IndexOutOfBoundsException e ) 118 | { 119 | // Ok; expected 120 | } 121 | } 122 | 123 | /** 124 | * Tests that we cannot call {@link CharBuffer#charAt(int)} with a negative 125 | * index fails. 126 | */ 127 | public void testCharWithNegativeIndexFail() 128 | { 129 | try 130 | { 131 | new CharBuffer( 1, 2, 3 ).charAt( -1 ); 132 | fail( "Expected IndexOutOfBoundsException!" ); 133 | } 134 | catch ( IndexOutOfBoundsException e ) 135 | { 136 | // Ok; expected 137 | } 138 | } 139 | 140 | /** 141 | * Tests that if two threads are concurrently appending and removing data, 142 | * that these appends occur atomically and do not fail. 143 | */ 144 | public void testConcurrentAppendAndRemoveOk() throws Exception 145 | { 146 | final CountDownLatch startLatch = new CountDownLatch( 1 ); 147 | final CountDownLatch stopLatch = new CountDownLatch( 2 ); 148 | 149 | final CharBuffer cb = new CharBuffer(); 150 | final int runCount = 10000; 151 | 152 | Thread t1 = new Thread() 153 | { 154 | @Override 155 | public void run() 156 | { 157 | Integer[] buffer = new Integer[] { 1, 2, 3, 4 }; 158 | int rc = runCount; 159 | 160 | try 161 | { 162 | startLatch.await( 5L, TimeUnit.SECONDS ); 163 | } 164 | catch ( InterruptedException exception ) 165 | { 166 | throw new RuntimeException( "Await failed!" ); 167 | } 168 | 169 | while ( rc-- > 0 ) 170 | { 171 | cb.append( buffer ); 172 | } 173 | 174 | stopLatch.countDown(); 175 | } 176 | }; 177 | 178 | Thread t2 = new Thread() 179 | { 180 | @Override 181 | public void run() 182 | { 183 | int rc = runCount; 184 | 185 | try 186 | { 187 | startLatch.await( 5L, TimeUnit.SECONDS ); 188 | } 189 | catch ( InterruptedException exception ) 190 | { 191 | throw new RuntimeException( "Await failed!" ); 192 | } 193 | 194 | while ( rc-- > 0 ) 195 | { 196 | if ( cb.length() > 4 ) 197 | { 198 | cb.removeUntil( 4 ); 199 | } 200 | } 201 | 202 | stopLatch.countDown(); 203 | } 204 | }; 205 | 206 | t1.start(); 207 | t2.start(); 208 | 209 | startLatch.countDown(); 210 | 211 | stopLatch.await( 10L, TimeUnit.SECONDS ); 212 | 213 | t1.join(); 214 | t2.join(); 215 | 216 | // Check that all appends & removes are performed atomically... 217 | for ( int i = 0; i < cb.length(); i += 4 ) 218 | { 219 | for ( int j = 0; j < 4; j++ ) 220 | { 221 | assertEquals( j + 1, cb.charAt( j + i ) ); 222 | } 223 | } 224 | } 225 | 226 | /** 227 | * Tests that if two threads are concurrently appending data, that these 228 | * appends occur atomically and do not fail. 229 | */ 230 | public void testConcurrentAppendsOk() throws Exception 231 | { 232 | final CountDownLatch startLatch = new CountDownLatch( 1 ); 233 | final CountDownLatch stopLatch = new CountDownLatch( 2 ); 234 | 235 | final CharBuffer cb = new CharBuffer(); 236 | final int runCount = 10000; 237 | 238 | Thread t1 = new Thread() 239 | { 240 | @Override 241 | public void run() 242 | { 243 | Integer[] buffer = new Integer[] { 1, 2, 3, 4 }; 244 | int rc = runCount; 245 | 246 | try 247 | { 248 | startLatch.await( 5L, TimeUnit.SECONDS ); 249 | } 250 | catch ( InterruptedException exception ) 251 | { 252 | throw new RuntimeException( "Await failed!" ); 253 | } 254 | 255 | while ( rc-- > 0 ) 256 | { 257 | cb.append( buffer ); 258 | } 259 | 260 | stopLatch.countDown(); 261 | } 262 | }; 263 | 264 | Thread t2 = new Thread() 265 | { 266 | @Override 267 | public void run() 268 | { 269 | Integer[] buffer = new Integer[] { 5, 6, 7, 8 }; 270 | int rc = runCount; 271 | 272 | try 273 | { 274 | startLatch.await( 5L, TimeUnit.SECONDS ); 275 | } 276 | catch ( InterruptedException exception ) 277 | { 278 | throw new RuntimeException( "Await failed!" ); 279 | } 280 | 281 | while ( rc-- > 0 ) 282 | { 283 | cb.append( buffer ); 284 | } 285 | 286 | stopLatch.countDown(); 287 | } 288 | }; 289 | 290 | t1.start(); 291 | t2.start(); 292 | 293 | startLatch.countDown(); 294 | 295 | stopLatch.await( 10L, TimeUnit.SECONDS ); 296 | 297 | t1.join(); 298 | t2.join(); 299 | 300 | // Check that we've got 2 * 4 * runCount characters in our buffer... 301 | assertEquals( 8 * runCount, cb.length() ); 302 | 303 | // Check that all appends are performed atomically... 304 | for ( int i = 0; i < cb.length(); i += 4 ) 305 | { 306 | assertTrue( ( cb.charAt( i ) == 1 ) || ( cb.charAt( i ) == 5 ) ); 307 | 308 | int offset = cb.charAt( i ) == 1 ? 1 : 5; 309 | for ( int j = i + 1; j < 4; j++ ) 310 | { 311 | assertEquals( j + offset, cb.charAt( j ) ); 312 | } 313 | } 314 | } 315 | 316 | /** 317 | * Tests that the length for an empty buffer is zero. 318 | */ 319 | public void testLengthEmptyBufferOk() 320 | { 321 | assertEquals( 0, new CharBuffer().length() ); 322 | } 323 | 324 | /** 325 | * Tests that the length for a non-empty buffer is correct. 326 | */ 327 | public void testLengthOk() 328 | { 329 | assertEquals( 3, new CharBuffer( 1, 2, 3 ).length() ); 330 | } 331 | 332 | /** 333 | * Tests that the length for buffer that is resized at least once is correct. 334 | */ 335 | public void testLengthWithInternalResizedArrayOk() 336 | { 337 | CharBuffer cb = new CharBuffer(); 338 | for ( int i = 0; i < 100; i++ ) 339 | { 340 | cb.append( i ); 341 | } 342 | assertEquals( 100, cb.length() ); 343 | } 344 | 345 | /** 346 | * Tests that calling {@link CharBuffer#removeUntil(int)} with a negative 347 | * position fails. 348 | */ 349 | public void testRemoveUntilWithNegativePositionFails() 350 | { 351 | try 352 | { 353 | new CharBuffer( 1, 2, 3 ).removeUntil( -1 ); 354 | fail( "Expected IndexOutOfBoundsException!" ); 355 | } 356 | catch ( IndexOutOfBoundsException e ) 357 | { 358 | // Ok; expected... 359 | } 360 | } 361 | 362 | /** 363 | * Tests that calling {@link CharBuffer#removeUntil(int)} with a negative 364 | * position fails. 365 | */ 366 | public void testRemoveUntilWithPositionBeyondLengthFails() 367 | { 368 | try 369 | { 370 | new CharBuffer( 1, 2, 3 ).removeUntil( 4 ); 371 | fail( "Expected IndexOutOfBoundsException!" ); 372 | } 373 | catch ( IndexOutOfBoundsException e ) 374 | { 375 | // Ok; expected... 376 | } 377 | } 378 | 379 | /** 380 | * Tests that calling {@link CharBuffer#removeUntil(int)} with a position of 381 | * two yields an empty charbuffer. 382 | */ 383 | public void testRemoveUntilWithPositionOneOk() 384 | { 385 | CharBuffer cb = new CharBuffer( 1, 2, 3 ); 386 | cb.removeUntil( 1 ); 387 | 388 | assertEquals( 2, cb.length() ); 389 | for ( int i = 0; i < cb.length(); i++ ) 390 | { 391 | assertEquals( ( char )( i + 2 ), cb.charAt( i ) ); 392 | } 393 | } 394 | 395 | /** 396 | * Tests that calling {@link CharBuffer#removeUntil(int)} with a position of 397 | * two yields an empty charbuffer. 398 | */ 399 | public void testRemoveUntilWithPositionThreeOk() 400 | { 401 | CharBuffer cb = new CharBuffer( 1, 2, 3 ); 402 | cb.removeUntil( 3 ); 403 | 404 | assertEquals( 0, cb.length() ); 405 | } 406 | 407 | /** 408 | * Tests that calling {@link CharBuffer#removeUntil(int)} with a position of 409 | * two yields an empty charbuffer. 410 | */ 411 | public void testRemoveUntilWithPositionTwoOk() 412 | { 413 | CharBuffer cb = new CharBuffer( 1, 2, 3 ); 414 | cb.removeUntil( 2 ); 415 | 416 | assertEquals( 1, cb.length() ); 417 | assertEquals( ( char )3, cb.charAt( 0 ) ); 418 | } 419 | 420 | /** 421 | * Tests that calling {@link CharBuffer#removeUntil(int)} with a position of 422 | * zero yields the same charbuffer. 423 | */ 424 | public void testRemoveUntilWithPositionZeroOk() 425 | { 426 | CharBuffer cb = new CharBuffer( 1, 2, 3 ); 427 | cb.removeUntil( 0 ); 428 | 429 | assertEquals( 3, cb.length() ); 430 | for ( int i = 0; i < 3; i++ ) 431 | { 432 | assertEquals( ( char )( i + 1 ), cb.charAt( i ) ); 433 | } 434 | } 435 | } 436 | -------------------------------------------------------------------------------- /test/nl/lxtreme/jvt220/terminal/vt220/DefaultTabulatorTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * jVT220 - Java VT220 terminal emulator. 3 | * 4 | * (C) Copyright 2012 - J.W. Janssen, . 5 | */ 6 | package nl.lxtreme.jvt220.terminal.vt220; 7 | 8 | 9 | import junit.framework.*; 10 | import nl.lxtreme.jvt220.terminal.vt220.AbstractTerminal.DefaultTabulator; 11 | 12 | 13 | /** 14 | * Test cases for {@link DefaultTabulator}. 15 | */ 16 | public class DefaultTabulatorTest extends TestCase 17 | { 18 | // VARIABLES 19 | 20 | private PlainTerminal m_term; 21 | private DefaultTabulator m_tabulator; 22 | 23 | // METHODS 24 | 25 | /** 26 | * Set up for each test case. 27 | */ 28 | protected void setUp() 29 | { 30 | m_term = new PlainTerminal( 80, 24 ); 31 | m_tabulator = m_term.new DefaultTabulator( 80, 8 ); 32 | } 33 | 34 | /** 35 | * Tests that we can clear all set tab stops. 36 | */ 37 | public void testClearAll() 38 | { 39 | m_tabulator.set( 1 ); 40 | m_tabulator.set( 3 ); 41 | 42 | assertFalse( m_tabulator.getTabStops().isEmpty() ); 43 | 44 | m_tabulator.clearAll(); 45 | 46 | assertTrue( m_tabulator.getTabStops().isEmpty() ); 47 | } 48 | 49 | /** 50 | * Tests that we can use the default tab stops. 51 | */ 52 | public void testNextTabWithoutAnyTabStops() 53 | { 54 | int tabStop = 4; // positions 55 | m_tabulator = m_term.new DefaultTabulator( 80, tabStop ); 56 | 57 | for ( int startPos = 0; startPos < tabStop; startPos++ ) 58 | { 59 | for ( int i = startPos, j = 4; i < 76; i += tabStop, j += tabStop ) 60 | { 61 | assertEquals( "StartPos = " + startPos + ", i = " + i, j, m_tabulator.nextTab( i ) ); 62 | } 63 | assertEquals( 79, m_tabulator.nextTab( 77 ) ); 64 | } 65 | } 66 | 67 | /** 68 | * Tests that we can use the tab stops. 69 | */ 70 | public void testNextTabWithTabStops() 71 | { 72 | m_tabulator.clearAll(); 73 | m_tabulator.set( 1 ); 74 | m_tabulator.set( 3 ); 75 | m_tabulator.set( 5 ); 76 | m_tabulator.set( 7 ); 77 | m_tabulator.set( 9 ); 78 | m_tabulator.set( 16 ); 79 | 80 | assertEquals( 1, m_tabulator.nextTab( 0 ) ); 81 | assertEquals( 3, m_tabulator.nextTab( 2 ) ); 82 | assertEquals( 5, m_tabulator.nextTab( 4 ) ); 83 | assertEquals( 7, m_tabulator.nextTab( 6 ) ); 84 | assertEquals( 9, m_tabulator.nextTab( 8 ) ); 85 | assertEquals( 16, m_tabulator.nextTab( 13 ) ); 86 | assertEquals( 79, m_tabulator.nextTab( 16 ) ); 87 | assertEquals( 79, m_tabulator.nextTab( 79 ) ); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /test/nl/lxtreme/jvt220/terminal/vt220/PlainTerminalTest.java: -------------------------------------------------------------------------------- 1 | package nl.lxtreme.jvt220.terminal.vt220; 2 | 3 | 4 | import java.io.*; 5 | 6 | import junit.framework.*; 7 | import nl.lxtreme.jvt220.terminal.ITerminal.ITextCell; 8 | 9 | 10 | /** 11 | * Test cases for {@link AbstractTerminal}. 12 | */ 13 | public class PlainTerminalTest extends TestCase 14 | { 15 | // METHODS 16 | 17 | public void testClearEnitreScreenOk() throws IOException 18 | { 19 | AbstractTerminal term = createTerminal(); 20 | term.moveCursorAbsolute( 2, 1 ); 21 | 22 | term.clearScreen( 2 ); 23 | assertEquals( " ", getTermText( term ) ); 24 | 25 | assertEquals( 2, term.getCursor().getX() ); 26 | assertEquals( 1, term.getCursor().getY() ); 27 | } 28 | 29 | public void testClearFirstLineOk() throws IOException 30 | { 31 | AbstractTerminal term = createTerminal(); 32 | term.moveCursorAbsolute( 0, 0 ); 33 | 34 | term.clearLine( 2 ); 35 | assertEquals( " 1234512345", getTermText( term ) ); 36 | 37 | assertEquals( 0, term.getCursor().getX() ); 38 | assertEquals( 0, term.getCursor().getY() ); 39 | } 40 | 41 | public void testClearLinePartiallyOk() throws IOException 42 | { 43 | AbstractTerminal term = createTerminal(); 44 | term.moveCursorAbsolute( 1, 1 ); 45 | 46 | term.clearLine( 1 ); 47 | assertEquals( "12345 34512345", getTermText( term ) ); 48 | 49 | assertEquals( 1, term.getCursor().getX() ); 50 | assertEquals( 1, term.getCursor().getY() ); 51 | 52 | term.clearLine( 0 ); 53 | assertEquals( "12345 12345", getTermText( term ) ); 54 | 55 | assertEquals( 1, term.getCursor().getX() ); 56 | assertEquals( 1, term.getCursor().getY() ); 57 | } 58 | 59 | public void testClearScreenAboveOk() throws IOException 60 | { 61 | AbstractTerminal term = createTerminal(); 62 | term.moveCursorAbsolute( 2, 1 ); 63 | 64 | term.clearScreen( 1 ); 65 | assertEquals( " 4512345", getTermText( term ) ); 66 | 67 | assertEquals( 2, term.getCursor().getX() ); 68 | assertEquals( 1, term.getCursor().getY() ); 69 | } 70 | 71 | public void testClearScreenBelowOk() throws IOException 72 | { 73 | AbstractTerminal term = createTerminal(); 74 | term.moveCursorAbsolute( 2, 1 ); 75 | 76 | term.clearScreen( 0 ); 77 | assertEquals( "1234512 ", getTermText( term ) ); 78 | 79 | assertEquals( 2, term.getCursor().getX() ); 80 | assertEquals( 1, term.getCursor().getY() ); 81 | } 82 | 83 | public void testClearSecondLineOk() throws IOException 84 | { 85 | AbstractTerminal term = createTerminal(); 86 | term.moveCursorAbsolute( 2, 1 ); 87 | 88 | term.clearLine( 2 ); 89 | assertEquals( "12345 12345", getTermText( term ) ); 90 | 91 | assertEquals( 2, term.getCursor().getX() ); 92 | assertEquals( 1, term.getCursor().getY() ); 93 | } 94 | 95 | public void testClearThirdLineOk() throws IOException 96 | { 97 | AbstractTerminal term = createTerminal(); 98 | term.moveCursorAbsolute( 1, 2 ); 99 | 100 | term.clearLine( 2 ); 101 | assertEquals( "1234512345 ", getTermText( term ) ); 102 | 103 | assertEquals( 1, term.getCursor().getX() ); 104 | assertEquals( 2, term.getCursor().getY() ); 105 | } 106 | 107 | public void testScrollDownOneLineOk() throws IOException 108 | { 109 | AbstractTerminal term = createTerminal(); 110 | term.moveCursorAbsolute( 1, 2 ); 111 | 112 | term.scrollDown( 1 ); 113 | assertEquals( " 1234512345", getTermText( term ) ); 114 | } 115 | 116 | public void testScrollDownThreeLinesOk() throws IOException 117 | { 118 | AbstractTerminal term = createTerminal(); 119 | term.moveCursorAbsolute( 1, 2 ); 120 | 121 | term.scrollDown( 3 ); 122 | assertEquals( " ", getTermText( term ) ); 123 | } 124 | 125 | public void testScrollDownTwoLinesOk() throws IOException 126 | { 127 | AbstractTerminal term = createTerminal(); 128 | term.moveCursorAbsolute( 1, 2 ); 129 | 130 | term.scrollDown( 2 ); 131 | assertEquals( " 12345", getTermText( term ) ); 132 | } 133 | 134 | public void testScrollDownWithScrollRegionOk() throws IOException 135 | { 136 | AbstractTerminal term = createTerminal( 5, 5 ); 137 | term.setAutoWrap( false ); 138 | term.read( "11111\r\n22222\r\n33333\r\n44444\r\n55555" ); 139 | term.setScrollRegion( 1, 3 ); // fixate first and last line... 140 | term.setOriginMode( true ); // make sure origin is retained... 141 | 142 | term.scrollDown( 2 ); 143 | assertEquals( "11111 2222255555", getTermText( term ) ); 144 | 145 | term.setScrollRegion( 0, 5 ); // default... 146 | 147 | term.scrollDown( 1 ); 148 | assertEquals( " 11111 22222", getTermText( term ) ); 149 | } 150 | 151 | public void testScrollUpOneLineOk() throws IOException 152 | { 153 | AbstractTerminal term = createTerminal( 5, 5 ); 154 | term.setAutoWrap( false ); 155 | term.read( "11111\r\n22222\r\n33333\r\n44444\r\n55555" ); 156 | term.moveCursorAbsolute( 1, 2 ); 157 | 158 | term.scrollUp( 1 ); 159 | assertEquals( "22222333334444455555 ", getTermText( term ) ); 160 | } 161 | 162 | public void testScrollUpThreeLinesOk() throws IOException 163 | { 164 | AbstractTerminal term = createTerminal(); 165 | term.moveCursorAbsolute( 1, 2 ); 166 | 167 | term.scrollUp( 3 ); 168 | assertEquals( " ", getTermText( term ) ); 169 | } 170 | 171 | public void testScrollUpTwoLinesOk() throws IOException 172 | { 173 | AbstractTerminal term = createTerminal(); 174 | term.moveCursorAbsolute( 1, 2 ); 175 | 176 | term.scrollUp( 2 ); 177 | assertEquals( "12345 ", getTermText( term ) ); 178 | } 179 | 180 | public void testScrollUpWithScrollRegionOk() throws IOException 181 | { 182 | AbstractTerminal term = createTerminal( 5, 5 ); 183 | term.setAutoWrap( false ); 184 | term.read( "11111\r\n22222\r\n33333\r\n44444\r\n55555" ); 185 | term.setScrollRegion( 1, 3 ); // fixate first and last line... 186 | term.setOriginMode( true ); // make sure origin is retained... 187 | 188 | term.scrollUp( 2 ); 189 | assertEquals( "1111144444 55555", getTermText( term ) ); 190 | 191 | term.setScrollRegion( 0, 5 ); // default... 192 | 193 | term.scrollUp( 1 ); 194 | assertEquals( "44444 55555 ", getTermText( term ) ); 195 | } 196 | 197 | public void testSetDimensionsEqualOk() throws IOException { 198 | AbstractTerminal term = createTerminal( "11111\r\n22222\r\n33333" ); 199 | 200 | assertEquals( "111112222233333", getTermText( term ) ); 201 | 202 | term.setDimensions( 5, 3 ); 203 | 204 | assertEquals( "111112222233333", getTermText( term ) ); 205 | } 206 | 207 | public void testSetDimensionsLargerOk() throws IOException { 208 | AbstractTerminal term = createTerminal( "11111\r\n22222\r\n33333" ); 209 | 210 | assertEquals( "111112222233333", getTermText( term ) ); 211 | 212 | term.setDimensions( 6, 3 ); 213 | 214 | assertEquals( "11111 22222 33333 ", getTermText( term ) ); 215 | 216 | term.setDimensions( 6, 4 ); 217 | 218 | assertEquals( "11111 22222 33333 ", getTermText( term ) ); 219 | } 220 | 221 | public void testSetDimensionsSmallerOk() throws IOException { 222 | AbstractTerminal term = createTerminal( "11111\r\n22222\r\n33333" ); 223 | 224 | assertEquals( "111112222233333", getTermText( term ) ); 225 | 226 | term.setDimensions( 4, 3 ); 227 | 228 | assertEquals( "111122223333", getTermText( term ) ); 229 | 230 | term.setDimensions( 4, 2 ); 231 | 232 | assertEquals( "11112222", getTermText( term ) ); 233 | } 234 | 235 | public void testWriteBackspacesAtLastPositionDoesNotScrollUpOk() throws IOException 236 | { 237 | AbstractTerminal term = createTerminal( "abcde\r\nfghij\r\nklmno" ); 238 | term.setAutoWrap( false ); 239 | term.moveCursorAbsolute( 5, 3 ); 240 | assertEquals( "abcdefghijklmno", getTermText( term ) ); 241 | 242 | term.read( "\b\b" ); 243 | assertEquals( "abcdefghijklm ", getTermText( term ) ); 244 | 245 | term.read( "op" ); 246 | assertEquals( "abcdefghijklmop", getTermText( term ) ); 247 | } 248 | 249 | public void testWriteTextAtLastPositionScrollUpOk() throws IOException 250 | { 251 | AbstractTerminal term = createTerminal( "abcde\r\nfghij\r\nklmno" ); 252 | term.moveCursorAbsolute( 5, 3 ); 253 | assertEquals( "abcdefghijklmno", getTermText( term ) ); 254 | 255 | term.read( "pq" ); 256 | assertEquals( "fghijklmnopq ", getTermText( term ) ); 257 | } 258 | 259 | public void testWriteTextHandlesNewlinesOk() throws IOException 260 | { 261 | AbstractTerminal term = createTerminal(); 262 | term.clearScreen( 2 ); 263 | term.moveCursorAbsolute( 0, 0 ); 264 | term.read( "ab\ncd\r\nef" ); 265 | assertEquals( "ab cd ef ", getTermText( term ) ); 266 | } 267 | 268 | /** 269 | * @return a new {@link AbstractTerminal} instance, never null. 270 | */ 271 | private PlainTerminal createTerminal() throws IOException 272 | { 273 | return createTerminal( "12345\r\n12345\r\n12345" ); 274 | } 275 | 276 | /** 277 | * @param aColumns 278 | * @param aLines 279 | * @return 280 | */ 281 | private PlainTerminal createTerminal( int aColumns, int aLines ) 282 | { 283 | return new PlainTerminal( aColumns, aLines ); 284 | } 285 | 286 | /** 287 | * @return a new {@link AbstractTerminal} instance, never null. 288 | */ 289 | private PlainTerminal createTerminal( String aText ) throws IOException 290 | { 291 | PlainTerminal term = createTerminal( 5, 3 ); 292 | term.setAutoWrap( false ); 293 | term.read( aText ); 294 | term.setAutoWrap( true ); 295 | return term; 296 | } 297 | 298 | /** 299 | * @param aTerm 300 | * @return 301 | */ 302 | private String getTermText( final AbstractTerminal aTerm ) 303 | { 304 | StringBuilder sb = new StringBuilder(); 305 | for ( int idx = aTerm.getFirstAbsoluteIndex(); idx <= aTerm.getLastAbsoluteIndex(); idx++ ) 306 | { 307 | ITextCell cell = aTerm.getCellAt( idx ); 308 | sb.append( cell == null ? ' ' : cell.getChar() ); 309 | } 310 | return sb.toString(); 311 | } 312 | } 313 | -------------------------------------------------------------------------------- /test/nl/lxtreme/jvt220/terminal/vt220/VT220ParserTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * jVT220 - Java VT220 terminal emulator. 3 | * 4 | * (C) Copyright 2012 - J.W. Janssen, . 5 | */ 6 | package nl.lxtreme.jvt220.terminal.vt220; 7 | 8 | import java.io.*; 9 | import java.util.*; 10 | 11 | import junit.framework.*; 12 | import nl.lxtreme.jvt220.terminal.vt220.VT220Parser.CSIType; 13 | import nl.lxtreme.jvt220.terminal.vt220.VT220Parser.VT220ParserHandler; 14 | 15 | 16 | /** 17 | * Test cases for {@link VT220Parser}. 18 | */ 19 | public class VT220ParserTest extends TestCase 20 | { 21 | // INNER TYPES 22 | 23 | static class VT220ParserTestAdapter implements VT220ParserHandler 24 | { 25 | @Override 26 | public void handleCharacter( char aChar ) throws IOException 27 | { 28 | fail( String.format( "Character (%c) seen?!", aChar ) ); 29 | } 30 | 31 | @Override 32 | public void handleControl( char aControlChar ) throws IOException 33 | { 34 | fail( String.format( "Control (%d) seen?!", ( int )aControlChar ) ); 35 | } 36 | 37 | @Override 38 | public void handleCSI( CSIType aType, int... aParameters ) throws IOException 39 | { 40 | fail( String.format( "CSI (%s %s) seen?!", aType, Arrays.toString( aParameters ) ) ); 41 | } 42 | 43 | @Override 44 | public void handleESC( char aDesignator, int... aParameters ) throws IOException 45 | { 46 | fail( String.format( "ESC %c %s seen?!", aDesignator, Arrays.toString( aParameters ) ) ); 47 | } 48 | } 49 | 50 | // VARIABLES 51 | 52 | private VT220Parser m_parser; 53 | 54 | // METHODS 55 | 56 | /** 57 | * Tests that the parser can handle control characters inside CSI sequences. 58 | */ 59 | public void testParseCSIWithControlCharactersOk() throws Exception 60 | { 61 | final int[] count = { 0 }; 62 | 63 | m_parser.parse( "\033[2\010C", new VT220ParserTestAdapter() 64 | { 65 | @Override 66 | public void handleControl( char aControlChar ) throws IOException 67 | { 68 | assertEquals( '\010', aControlChar ); 69 | count[0]++; 70 | } 71 | 72 | @Override 73 | public void handleCSI( CSIType aType, int... aParameters ) throws IOException 74 | { 75 | assertEquals( CSIType.CUF, aType ); 76 | assertEquals( 2, aParameters[0] ); 77 | count[0]++; 78 | } 79 | } ); 80 | 81 | assertEquals( 2, count[0] ); 82 | } 83 | 84 | /** 85 | * Test method for parsing CUP sequences. 86 | */ 87 | public void testParseCUP() throws Exception 88 | { 89 | final int count[] = { 0 }; 90 | m_parser.parse( "\033[1;2H", new VT220ParserTestAdapter() 91 | { 92 | @Override 93 | public void handleCSI( CSIType aType, int... aParameters ) throws IOException 94 | { 95 | assertEquals( CSIType.CUP, aType ); 96 | assertEquals( 1, aParameters[0] ); 97 | assertEquals( 2, aParameters[1] ); 98 | count[0]++; 99 | } 100 | } ); 101 | assertEquals( 1, count[0] ); 102 | 103 | count[0] = 0; 104 | m_parser.parse( "\033[10;20H", new VT220ParserTestAdapter() 105 | { 106 | @Override 107 | public void handleCSI( CSIType aType, int... aParameters ) throws IOException 108 | { 109 | assertEquals( CSIType.CUP, aType ); 110 | assertEquals( 10, aParameters[0] ); 111 | assertEquals( 20, aParameters[1] ); 112 | count[0]++; 113 | } 114 | } ); 115 | assertEquals( 1, count[0] ); 116 | } 117 | 118 | /** 119 | * Tests that the parsing for character set designations works. 120 | */ 121 | public void testParseDesignateCharacterSetOk() throws Exception 122 | { 123 | final int count[] = { 0 }; 124 | m_parser.parse( "\033(A", new VT220ParserTestAdapter() 125 | { 126 | @Override 127 | public void handleESC( char aDesignator, int... aParameters ) throws IOException 128 | { 129 | assertEquals( '(', aDesignator ); 130 | assertEquals( 'A', aParameters[0] ); 131 | count[0]++; 132 | } 133 | } ); 134 | assertEquals( 1, count[0] ); 135 | 136 | count[0] = 0; 137 | m_parser.parse( "\033)B", new VT220ParserTestAdapter() 138 | { 139 | @Override 140 | public void handleESC( char aDesignator, int... aParameters ) throws IOException 141 | { 142 | assertEquals( ')', aDesignator ); 143 | assertEquals( 'B', aParameters[0] ); 144 | count[0]++; 145 | } 146 | } ); 147 | assertEquals( 1, count[0] ); 148 | 149 | count[0] = 0; 150 | m_parser.parse( "\033*C", new VT220ParserTestAdapter() 151 | { 152 | @Override 153 | public void handleESC( char aDesignator, int... aParameters ) throws IOException 154 | { 155 | assertEquals( '*', aDesignator ); 156 | assertEquals( 'C', aParameters[0] ); 157 | count[0]++; 158 | } 159 | } ); 160 | assertEquals( 1, count[0] ); 161 | 162 | count[0] = 0; 163 | m_parser.parse( "\033+D", new VT220ParserTestAdapter() 164 | { 165 | @Override 166 | public void handleESC( char aDesignator, int... aParameters ) throws IOException 167 | { 168 | assertEquals( '+', aDesignator ); 169 | assertEquals( 'D', aParameters[0] ); 170 | count[0]++; 171 | } 172 | } ); 173 | assertEquals( 1, count[0] ); 174 | } 175 | 176 | /** 177 | * Tests that a save-cursor directive followed by a CSI is correctly parsed. 178 | */ 179 | public void testParseSaveCursorSequenceWithCUP() throws Exception 180 | { 181 | final int[] count = { 0 }; 182 | 183 | m_parser.parse( "\0337\033[1;1H", new VT220ParserTestAdapter() 184 | { 185 | @Override 186 | public void handleCSI( CSIType aType, int... aParameters ) throws IOException 187 | { 188 | assertEquals( CSIType.CUP, aType ); 189 | assertEquals( 1, aParameters[0] ); 190 | assertEquals( 1, aParameters[1] ); 191 | count[0]++; 192 | } 193 | 194 | @Override 195 | public void handleESC( char aDesignator, int... aParameters ) throws IOException 196 | { 197 | assertEquals( '7', aDesignator ); 198 | count[0]++; 199 | } 200 | } ); 201 | 202 | assertEquals( 2, count[0] ); 203 | } 204 | 205 | /** 206 | * Set up for each test case. 207 | */ 208 | protected void setUp() throws Exception 209 | { 210 | m_parser = new VT220Parser(); 211 | m_parser.setLogLevel( 1 ); 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /test/nl/lxtreme/jvt220/terminal/vt220/VT220TerminalTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * jVT220 - Java VT220 terminal emulator. 3 | * 4 | * (C) Copyright 2012 - J.W. Janssen, . 5 | */ 6 | package nl.lxtreme.jvt220.terminal.vt220; 7 | 8 | 9 | import java.io.*; 10 | 11 | import junit.framework.*; 12 | 13 | 14 | /** 15 | * Test cases for {@link VT220Terminal}. 16 | */ 17 | public class VT220TerminalTest extends TestCase 18 | { 19 | // VARIABLES 20 | 21 | private VT220Terminal m_terminal; 22 | private ByteArrayOutputStream m_buffer; 23 | 24 | // METHODS 25 | 26 | /** 27 | * Tests that the insert mode works. 28 | */ 29 | public void testInsertModeOk() throws IOException 30 | { 31 | m_terminal.read( "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" ); 32 | m_terminal.read( "\033[2;1H\033[0J\033[1;2H" ); 33 | m_terminal.read( "B" ); 34 | m_terminal.read( "\033[1D\033[4h" ); 35 | m_terminal.read( "******************************************************************************" ); 36 | m_terminal.read( "\033[4l\033[4;1H" ); 37 | 38 | assertEquals( 'A', m_terminal.getCellAt( 0, 0 ).getChar() ); 39 | assertEquals( 'B', m_terminal.getCellAt( 79, 0 ).getChar() ); 40 | } 41 | 42 | 43 | /** 44 | * Tests that the auto-wrap mode is handled correctly in combination with the 45 | * backward movement of the cursor. 46 | */ 47 | public void testAutoWrapMoveCursorBackOk() throws IOException 48 | { 49 | // Cursor back after a wrap-around means we're going back onto the previous 50 | // line... 51 | m_terminal.read( "\033[2J\033[2;1H*\033[2;80H*\033[10D\033E*" ); 52 | 53 | assertEquals( '*', m_terminal.getCellAt( 0, 1 ).getChar() ); 54 | assertEquals( '*', m_terminal.getCellAt( 79, 1 ).getChar() ); 55 | assertEquals( '*', m_terminal.getCellAt( 0, 2 ).getChar() ); 56 | 57 | // If a character is written after a wrap-around, cursor back won't go back 58 | // onto previous line... 59 | m_terminal.read( "\033[2J\033[2;1H*\033[2;80H**\033[10D\033E*" ); 60 | 61 | assertEquals( '*', m_terminal.getCellAt( 0, 1 ).getChar() ); 62 | assertEquals( '*', m_terminal.getCellAt( 79, 1 ).getChar() ); 63 | assertEquals( '*', m_terminal.getCellAt( 0, 3 ).getChar() ); 64 | 65 | // Cursor back after a wrap-around means we're going back onto the previous 66 | // line... 67 | m_terminal.read( "\033[2J\033[2;1H*\033[2;80H*\033[10D\015\012*" ); 68 | 69 | assertEquals( '*', m_terminal.getCellAt( 0, 1 ).getChar() ); 70 | assertEquals( '*', m_terminal.getCellAt( 79, 1 ).getChar() ); 71 | assertEquals( '*', m_terminal.getCellAt( 0, 2 ).getChar() ); 72 | } 73 | 74 | /** 75 | * Tests that the auto-wrap mode is handled correctly in various 76 | * circumstances. 77 | */ 78 | public void testAutoWrapOk() throws IOException 79 | { 80 | m_terminal.read( "\033[3;21r\033[?6h" ); 81 | 82 | for ( int i = 0; i < 26; i++ ) 83 | { 84 | char rightChar = ( char )( i + 'a' ); 85 | char leftChar = ( char )( i + 'A' ); 86 | 87 | switch ( i % 4 ) 88 | { 89 | case 0: 90 | /* draw characters as-is, for reference */ 91 | m_terminal.read( "\033[19;1H" + leftChar ); 92 | m_terminal.read( "\033[19;80H" + rightChar ); 93 | m_terminal.read( "\015\012" ); 94 | break; 95 | case 1: 96 | /* simple wrapping */ 97 | m_terminal.read( "\033[18;80H" + ( char )( rightChar - 1 ) + leftChar ); 98 | m_terminal.read( "\033[19;80H" + leftChar + "\010 " + rightChar ); 99 | m_terminal.read( "\015\012" ); 100 | break; 101 | case 2: 102 | /* tab to right margin */ 103 | m_terminal.read( "\033[19;80H" + leftChar + "\010\010\011\011" + rightChar ); 104 | m_terminal.read( "\033[19;2H\010" + leftChar ); 105 | m_terminal.read( "\015\012" ); 106 | break; 107 | default: 108 | /* newline at right margin */ 109 | m_terminal.read( "\033[19;80H\015\012" ); 110 | m_terminal.read( "\033[18;1H" + leftChar ); 111 | m_terminal.read( "\033[18;80H" + rightChar ); 112 | break; 113 | } 114 | } 115 | 116 | // At column zero we should have I..Z, and at the last column, we should 117 | // have i..z; 118 | for ( int row = 2; row < 20; row++ ) 119 | { 120 | char left = ( char )( 'G' + row ); 121 | char right = ( char )( 'g' + row ); 122 | 123 | assertEquals( left, m_terminal.getCellAt( 0, row ).getChar() ); 124 | assertEquals( right, m_terminal.getCellAt( 79, row ).getChar() ); 125 | } 126 | } 127 | 128 | /** 129 | * Tests that the scroll down function works correctly. 130 | */ 131 | public void testScrollDownOk() throws IOException 132 | { 133 | int max = m_terminal.getLastScrollLine(); 134 | int last = max - 3; 135 | 136 | for ( int n = 1; n < last; n++ ) 137 | { 138 | m_terminal.read( String.format( "\033[%1$d;%1$dH", n ) ); 139 | m_terminal.read( "*" ); 140 | m_terminal.read( "\033[1T" ); 141 | } 142 | 143 | for ( int n = 1; n < last; n++ ) 144 | { 145 | assertEquals( '*', m_terminal.getCellAt( n - 1, last - 1 ).getChar() ); 146 | } 147 | } 148 | 149 | /** 150 | * Tests that the movement of the cursor is bound to the terminal dimensions. 151 | */ 152 | public void testMoveCursorBoundToTerminalDimensionsOk() throws IOException 153 | { 154 | m_terminal.read( "\033[999;999H" ); 155 | m_terminal.read( "\033[6n" ); 156 | 157 | String response = m_buffer.toString(); 158 | assertEquals( "\033[24;80R", response ); 159 | } 160 | 161 | /** 162 | * Set up for this test case. 163 | */ 164 | protected void setUp() throws Exception 165 | { 166 | m_buffer = new ByteArrayOutputStream(); 167 | m_terminal = new VT220Terminal( 80, 24 ) 168 | { 169 | @Override 170 | public int write( CharSequence aResponse ) throws IOException 171 | { 172 | for ( int i = 0; i < aResponse.length(); i++ ) 173 | { 174 | m_buffer.write( aResponse.charAt( i ) ); 175 | } 176 | return aResponse.length(); 177 | } 178 | }; 179 | } 180 | } 181 | --------------------------------------------------------------------------------