Catch up to j3270
Build and Test a3270 / Build Android APK (push) Successful in 7m33s

This commit is contained in:
2026-09-04 20:58:34 -04:00
parent e82d01003b
commit f77f51474c
9 changed files with 1091 additions and 73 deletions
@@ -191,6 +191,29 @@ class MainActivity : ComponentActivity() {
val alt = event.isAltPressed || (event.metaState and KeyEvent.META_ALT_ON != 0) val alt = event.isAltPressed || (event.metaState and KeyEvent.META_ALT_ON != 0)
// 1. Function Keys (F1..F12 -> PF1..PF12 or PF13..PF24 with Shift or Alt) // 1. Function Keys (F1..F12 -> PF1..PF12 or PF13..PF24 with Shift or Alt)
// Mode toggles matching j3270:
// Alt+F1: DOC mode (Entry Assist)
// Alt+F2: Word Wrap
// Alt+F3: APL Mode
if (alt && keyCode == KeyEvent.KEYCODE_F1) {
viewModel.toggleDocMode()
val isDoc = viewModel.isDocMode.value
Toast.makeText(this, "DOC Mode: " + (if (isDoc) "ON" else "OFF"), Toast.LENGTH_SHORT).show()
return true
}
if (alt && keyCode == KeyEvent.KEYCODE_F2) {
viewModel.toggleWordWrap()
val isWrap = viewModel.isWordWrap.value
Toast.makeText(this, "Word Wrap: " + (if (isWrap) "ON" else "OFF"), Toast.LENGTH_SHORT).show()
return true
}
if (alt && keyCode == KeyEvent.KEYCODE_F3) {
viewModel.toggleAplMode()
val isApl = viewModel.isAplMode.value
Toast.makeText(this, "APL Mode: " + (if (isApl) "ON" else "OFF"), Toast.LENGTH_SHORT).show()
return true
}
if (keyCode in KeyEvent.KEYCODE_F1..KeyEvent.KEYCODE_F12) { if (keyCode in KeyEvent.KEYCODE_F1..KeyEvent.KEYCODE_F12) {
val fNum = keyCode - KeyEvent.KEYCODE_F1 + 1 val fNum = keyCode - KeyEvent.KEYCODE_F1 + 1
val pfNum = if (shift || alt) fNum + 12 else fNum val pfNum = if (shift || alt) fNum + 12 else fNum
@@ -339,6 +362,10 @@ class MainActivity : ComponentActivity() {
// 5. Standard Navigation & Special Keys // 5. Standard Navigation & Special Keys
when (keyCode) { when (keyCode) {
KeyEvent.KEYCODE_INSERT -> {
viewModel.toggleInsert()
return true
}
KeyEvent.KEYCODE_DPAD_LEFT -> { KeyEvent.KEYCODE_DPAD_LEFT -> {
viewModel.cursorLeft() viewModel.cursorLeft()
return true return true
@@ -503,6 +530,10 @@ fun MainScreen(
val blockSelectMode by viewModel.blockSelectMode.collectAsState() val blockSelectMode by viewModel.blockSelectMode.collectAsState()
val uiTheme by viewModel.uiTheme.collectAsState() val uiTheme by viewModel.uiTheme.collectAsState()
val isInsertMode by viewModel.isInsertMode.collectAsState() val isInsertMode by viewModel.isInsertMode.collectAsState()
val isDocMode by viewModel.isDocMode.collectAsState()
val isWordWrap by viewModel.isWordWrap.collectAsState()
val isAplMode by viewModel.isAplMode.collectAsState()
val fourColorOverride by viewModel.fourColorOverride.collectAsState()
val isNumericField = remember(screenBuffer, cursorAddr, screenVersion) { val isNumericField = remember(screenBuffer, cursorAddr, screenVersion) {
if (screenBuffer != null && screenBuffer?.isFormatted == true) { if (screenBuffer != null && screenBuffer?.isFormatted == true) {
@@ -510,15 +541,6 @@ fun MainScreen(
(fa.toInt() and haus.nightmare.lib3270j.protocol.DS3270Constants.FA_NUMERIC) != 0 (fa.toInt() and haus.nightmare.lib3270j.protocol.DS3270Constants.FA_NUMERIC) != 0
} else false } else false
} }
val modelName = remember(rows, cols) {
when {
rows == 24 && cols == 80 -> "3279-2"
rows == 32 && cols == 80 -> "3279-3"
rows == 43 && cols == 80 -> "3279-4"
rows == 27 && cols == 132 -> "3279-5"
else -> "DYNAMIC"
}
}
var showConnectDialog by remember { mutableStateOf(false) } var showConnectDialog by remember { mutableStateOf(false) }
var showFtDialog by remember { mutableStateOf(false) } var showFtDialog by remember { mutableStateOf(false) }
@@ -636,6 +658,7 @@ fun MainScreen(
cursorStyle = cursorStyle, cursorStyle = cursorStyle,
isInsertMode = isInsertMode, isInsertMode = isInsertMode,
blockSelectMode = blockSelectMode, blockSelectMode = blockSelectMode,
fourColorOverride = fourColorOverride,
onPasteLineWrap = { text, wrapCol, wordWrap -> onPasteLineWrap = { text, wrapCol, wordWrap ->
viewModel.pasteLineWrap(text, wrapCol, wordWrap) viewModel.pasteLineWrap(text, wrapCol, wordWrap)
}, },
@@ -691,6 +714,11 @@ fun MainScreen(
connectionState = connectionState, connectionState = connectionState,
isShiftPressed = isShiftPressed, isShiftPressed = isShiftPressed,
isLightPenMode = isLightPenMode, isLightPenMode = isLightPenMode,
isDocMode = isDocMode,
isWordWrap = isWordWrap,
isAplMode = isAplMode,
isInsertMode = isInsertMode,
fourColorOverride = fourColorOverride,
hapticFeedbackEnabled = hapticFeedback, hapticFeedbackEnabled = hapticFeedback,
onClearShift = onClearShift, onClearShift = onClearShift,
onConnectClick = { showConnectDialog = true }, onConnectClick = { showConnectDialog = true },
@@ -702,6 +730,15 @@ fun MainScreen(
onScriptClick = { showScriptDialog = true }, onScriptClick = { showScriptDialog = true },
onPrinterSessionClick = { showPrinterSessionDialog = true }, onPrinterSessionClick = { showPrinterSessionDialog = true },
onToggleLightPen = { viewModel.toggleLightPen() }, onToggleLightPen = { viewModel.toggleLightPen() },
onToggleDocMode = { viewModel.toggleDocMode() },
onToggleWordWrap = { viewModel.toggleWordWrap() },
onToggleAplMode = { viewModel.toggleAplMode() },
onToggleInsert = { viewModel.toggleInsert() },
onToggleFourColorOverride = {
val next = !fourColorOverride
viewModel.setFourColorOverride(next)
AppSettings.setFourColorOverride(context, next)
},
onSettingsClick = { showSettingsDialog = true }, onSettingsClick = { showSettingsDialog = true },
onSendAid = { aid -> onSendAid = { aid ->
viewModel.sendAid(aid) viewModel.sendAid(aid)
@@ -769,13 +806,18 @@ fun MainScreen(
isTls = isTlsActive, isTls = isTlsActive,
isTlsVerified = isTlsVerified, isTlsVerified = isTlsVerified,
graphicsMode = defaultGraphicsMode, graphicsMode = defaultGraphicsMode,
codePage = codePage,
isLightPenMode = isLightPenMode, isLightPenMode = isLightPenMode,
isInsertMode = isInsertMode, isInsertMode = isInsertMode,
isDocMode = isDocMode,
isWordWrap = isWordWrap,
isAplMode = isAplMode,
inhibitReason = viewModel.getClient()?.oia?.inputInhibited ?: 0, inhibitReason = viewModel.getClient()?.oia?.inputInhibited ?: 0,
luName = viewModel.getClient()?.telnetFSM?.connectedLu ?: "", luName = viewModel.getClient()?.telnetFSM?.connectedLu ?: "",
isNumericField = isNumericField, isNumericField = isNumericField,
modelName = modelName onToggleDocMode = { viewModel.toggleDocMode() },
onToggleWordWrap = { viewModel.toggleWordWrap() },
onToggleAplMode = { viewModel.toggleAplMode() },
onToggleInsert = { viewModel.toggleInsert() }
) )
} }
} }
@@ -825,8 +867,20 @@ fun MainScreen(
initialStartupBehavior = AppSettings.getStartupBehavior(context).name, initialStartupBehavior = AppSettings.getStartupBehavior(context).name,
initialDynamicRows = AppSettings.getDynamicRows(context), initialDynamicRows = AppSettings.getDynamicRows(context),
initialDynamicCols = AppSettings.getDynamicCols(context), initialDynamicCols = AppSettings.getDynamicCols(context),
initialEntryAssistDocMode = isDocMode,
initialEntryAssistWordWrap = isWordWrap,
initialEntryAssistStartCol = AppSettings.getEntryAssistStartCol(context),
initialEntryAssistEndCol = AppSettings.getEntryAssistEndCol(context),
initialEntryAssistBell = AppSettings.getEntryAssistBell(context),
initialEntryAssistBellCol = AppSettings.getEntryAssistBellCol(context),
initialEntryAssistTabStops = AppSettings.getEntryAssistTabStops(context),
initialInsertOffOnAid = AppSettings.getInsertOffOnAid(context),
initialFourColorOverride = fourColorOverride,
initialNumericFieldLock = AppSettings.getNumericFieldLock(context),
initialAutoSkipEnabled = AppSettings.getAutoSkipEnabled(context),
onDismiss = { showSettingsDialog = false }, onDismiss = { showSettingsDialog = false },
onSave = { mask, blink, haptic, verify, gfx, defaultCp, theme, crosshair, style, blockSel, startup, dynRows, dynCols -> onSave = { mask, blink, haptic, verify, gfx, defaultCp, theme, crosshair, style, blockSel, startup, dynRows, dynCols,
doc, wrap, startCol, endCol, bell, bellCol, tabs, resetIns, fourCol, numLk, autoSk ->
showSettingsDialog = false showSettingsDialog = false
viewModel.updateSettings(mask, blink, haptic, verify, gfx, defaultCp) viewModel.updateSettings(mask, blink, haptic, verify, gfx, defaultCp)
viewModel.setUiTheme(theme) viewModel.setUiTheme(theme)
@@ -836,6 +890,18 @@ fun MainScreen(
AppSettings.setStartupBehavior(context, AppSettings.StartupBehavior.valueOf(startup)) AppSettings.setStartupBehavior(context, AppSettings.StartupBehavior.valueOf(startup))
AppSettings.setDynamicRows(context, dynRows) AppSettings.setDynamicRows(context, dynRows)
AppSettings.setDynamicCols(context, dynCols) AppSettings.setDynamicCols(context, dynCols)
AppSettings.setEntryAssistDocMode(context, doc)
AppSettings.setEntryAssistWordWrap(context, wrap)
AppSettings.setEntryAssistStartCol(context, startCol)
AppSettings.setEntryAssistEndCol(context, endCol)
AppSettings.setEntryAssistBell(context, bell)
AppSettings.setEntryAssistBellCol(context, bellCol)
AppSettings.setEntryAssistTabStops(context, tabs)
AppSettings.setInsertOffOnAid(context, resetIns)
AppSettings.setFourColorOverride(context, fourCol)
AppSettings.setNumericFieldLock(context, numLk)
AppSettings.setAutoSkipEnabled(context, autoSk)
viewModel.reloadSettings()
terminalInputViewRef?.showSoftKeyboard() terminalInputViewRef?.showSoftKeyboard()
} }
) )
@@ -53,6 +53,12 @@ sealed interface TerminalInputAction {
data object Dup : TerminalInputAction data object Dup : TerminalInputAction
data object FieldMark : TerminalInputAction data object FieldMark : TerminalInputAction
data object ToggleInsert : TerminalInputAction data object ToggleInsert : TerminalInputAction
data object ToggleDocMode : TerminalInputAction
data object ToggleWordWrap : TerminalInputAction
data object ToggleApl : TerminalInputAction
data class SetDocMode(val enabled: Boolean) : TerminalInputAction
data class SetWordWrap(val enabled: Boolean) : TerminalInputAction
data class SetAplMode(val enabled: Boolean) : TerminalInputAction
data class SetCursor(val baddr: Int) : TerminalInputAction data class SetCursor(val baddr: Int) : TerminalInputAction
data class LightPenSelect(val baddr: Int) : TerminalInputAction data class LightPenSelect(val baddr: Int) : TerminalInputAction
} }
@@ -161,6 +167,18 @@ class TerminalViewModel(application: Application) : AndroidViewModel(application
private val _isInsertMode = MutableStateFlow(false) private val _isInsertMode = MutableStateFlow(false)
val isInsertMode: StateFlow<Boolean> = _isInsertMode.asStateFlow() val isInsertMode: StateFlow<Boolean> = _isInsertMode.asStateFlow()
private val _isDocMode = MutableStateFlow(AppSettings.getEntryAssistDocMode(application))
val isDocMode: StateFlow<Boolean> = _isDocMode.asStateFlow()
private val _isWordWrap = MutableStateFlow(AppSettings.getEntryAssistWordWrap(application))
val isWordWrap: StateFlow<Boolean> = _isWordWrap.asStateFlow()
private val _isAplMode = MutableStateFlow(false)
val isAplMode: StateFlow<Boolean> = _isAplMode.asStateFlow()
private val _fourColorOverride = MutableStateFlow(AppSettings.getFourColorOverride(application))
val fourColorOverride: StateFlow<Boolean> = _fourColorOverride.asStateFlow()
fun toggleLightPen() { fun toggleLightPen() {
_isLightPenMode.value = !_isLightPenMode.value _isLightPenMode.value = !_isLightPenMode.value
} }
@@ -193,6 +211,74 @@ class TerminalViewModel(application: Application) : AndroidViewModel(application
_uiTheme.value = theme _uiTheme.value = theme
} }
fun toggleDocMode() {
inputChannel.trySend(TerminalInputAction.ToggleDocMode)
}
fun setDocMode(enabled: Boolean) {
inputChannel.trySend(TerminalInputAction.SetDocMode(enabled))
}
fun toggleWordWrap() {
inputChannel.trySend(TerminalInputAction.ToggleWordWrap)
}
fun setWordWrap(enabled: Boolean) {
inputChannel.trySend(TerminalInputAction.SetWordWrap(enabled))
}
fun toggleAplMode() {
inputChannel.trySend(TerminalInputAction.ToggleApl)
}
fun setAplMode(enabled: Boolean) {
inputChannel.trySend(TerminalInputAction.SetAplMode(enabled))
}
fun setFourColorOverride(override: Boolean) {
AppSettings.setFourColorOverride(getApplication(), override)
_fourColorOverride.value = override
_screenVersion.value = System.currentTimeMillis()
}
fun toggleFourColorOverride() {
setFourColorOverride(!_fourColorOverride.value)
}
fun playBellSound() {
try {
val tg = android.media.ToneGenerator(android.media.AudioManager.STREAM_NOTIFICATION, 80)
tg.startTone(android.media.ToneGenerator.TONE_PROP_BEEP, 150)
} catch (_: Throwable) {}
}
fun applyModeSettings() {
val app = getApplication<Application>()
val c = client ?: return
val sb = c.screenBuffer
if (sb != null) {
sb.isEntryAssistDOCmode = AppSettings.getEntryAssistDocMode(app)
sb.isEntryAssistWordWrap = AppSettings.getEntryAssistWordWrap(app)
sb.setLeftMargin((AppSettings.getEntryAssistStartCol(app) - 1).coerceAtLeast(0))
sb.setRightMargin((AppSettings.getEntryAssistEndCol(app) - 1).coerceAtLeast(0))
sb.setWordTabPositions(AppSettings.getEntryAssistTabStopsArray(app))
}
val ip = c.inputProcessor
if (ip != null) {
ip.isBellEnabled = AppSettings.getEntryAssistBell(app)
ip.bellColumn = (AppSettings.getEntryAssistBellCol(app) - 1).coerceAtLeast(0)
ip.isInsertOffOnAid = AppSettings.getInsertOffOnAid(app)
ip.isNumericFieldLock = AppSettings.getNumericFieldLock(app)
ip.isAutoSkipEnabled = AppSettings.getAutoSkipEnabled(app)
}
_fourColorOverride.value = AppSettings.getFourColorOverride(app)
_isDocMode.value = c.isDocMode
_isWordWrap.value = c.isWordWrap
_isAplMode.value = c.isAplMode
_isInsertMode.value = c.isInsertMode
_screenVersion.value = System.currentTimeMillis()
}
fun reloadSettings() { fun reloadSettings() {
val app = getApplication<Application>() val app = getApplication<Application>()
_maskHiddenInput.value = AppSettings.isMaskHiddenInputEnabled(app) _maskHiddenInput.value = AppSettings.isMaskHiddenInputEnabled(app)
@@ -205,6 +291,8 @@ class TerminalViewModel(application: Application) : AndroidViewModel(application
_cursorStyle.value = AppSettings.getCursorStyle(app) _cursorStyle.value = AppSettings.getCursorStyle(app)
_blockSelectMode.value = AppSettings.isBlockSelectModeEnabled(app) _blockSelectMode.value = AppSettings.isBlockSelectModeEnabled(app)
_uiTheme.value = AppSettings.getUiTheme(app) _uiTheme.value = AppSettings.getUiTheme(app)
_fourColorOverride.value = AppSettings.getFourColorOverride(app)
applyModeSettings()
} }
fun pasteLineWrap(text: String, endCol: Int, wordWrap: Boolean) { fun pasteLineWrap(text: String, endCol: Int, wordWrap: Boolean) {
@@ -472,8 +560,48 @@ class TerminalViewModel(application: Application) : AndroidViewModel(application
} }
is TerminalInputAction.ToggleInsert -> { is TerminalInputAction.ToggleInsert -> {
if (!isNvt) { if (!isNvt) {
ip.processToggleInsert() c.toggleInsert()
_isInsertMode.value = ip.isInsertMode _isInsertMode.value = c.isInsertMode
}
}
is TerminalInputAction.ToggleDocMode -> {
if (!isNvt) {
c.toggleDocMode()
AppSettings.setEntryAssistDocMode(getApplication(), c.isDocMode)
_isDocMode.value = c.isDocMode
}
}
is TerminalInputAction.ToggleWordWrap -> {
if (!isNvt) {
c.toggleWordWrap()
AppSettings.setEntryAssistWordWrap(getApplication(), c.isWordWrap)
_isWordWrap.value = c.isWordWrap
}
}
is TerminalInputAction.ToggleApl -> {
if (!isNvt) {
c.toggleAplMode()
_isAplMode.value = c.isAplMode
}
}
is TerminalInputAction.SetDocMode -> {
if (!isNvt) {
c.isDocMode = action.enabled
AppSettings.setEntryAssistDocMode(getApplication(), action.enabled)
_isDocMode.value = action.enabled
}
}
is TerminalInputAction.SetWordWrap -> {
if (!isNvt) {
c.isWordWrap = action.enabled
AppSettings.setEntryAssistWordWrap(getApplication(), action.enabled)
_isWordWrap.value = action.enabled
}
}
is TerminalInputAction.SetAplMode -> {
if (!isNvt) {
c.isAplMode = action.enabled
_isAplMode.value = action.enabled
} }
} }
is TerminalInputAction.SetCursor -> { is TerminalInputAction.SetCursor -> {
@@ -769,6 +897,20 @@ class TerminalViewModel(application: Application) : AndroidViewModel(application
_isKeyboardLocked.value = locked _isKeyboardLocked.value = locked
} }
newClient.inputProcessor.setBellListener {
playBellSound()
}
newClient.oia.registerOIAListener(object : haus.nightmare.lib3270j.ecl.ECLOIAListener {
override fun oiaChanged(event: haus.nightmare.lib3270j.ecl.ECLOIAEvent) {
_isInsertMode.value = newClient.isInsertMode
_isDocMode.value = newClient.isDocMode
_isWordWrap.value = newClient.isWordWrap
_isAplMode.value = newClient.isAplMode
_screenVersion.value = System.currentTimeMillis()
}
})
newClient.addScreenUpdateListener(object : ScreenUpdateListener { newClient.addScreenUpdateListener(object : ScreenUpdateListener {
override fun onScreenUpdated() { override fun onScreenUpdated() {
val buf = newClient.screenBuffer val buf = newClient.screenBuffer
@@ -779,6 +921,10 @@ class TerminalViewModel(application: Application) : AndroidViewModel(application
_cursorAddress.value = buf.cursorAddress _cursorAddress.value = buf.cursorAddress
} }
_isKeyboardLocked.value = newClient.inputProcessor.isKeyboardLocked _isKeyboardLocked.value = newClient.inputProcessor.isKeyboardLocked
_isInsertMode.value = newClient.isInsertMode
_isDocMode.value = newClient.isDocMode
_isWordWrap.value = newClient.isWordWrap
_isAplMode.value = newClient.isAplMode
_screenVersion.value = System.currentTimeMillis() _screenVersion.value = System.currentTimeMillis()
// Drive CUT mode file transfers if active // Drive CUT mode file transfers if active
@@ -804,6 +950,7 @@ class TerminalViewModel(application: Application) : AndroidViewModel(application
override fun onSoundAlarm() { override fun onSoundAlarm() {
log.fine("Sound Alarm") log.fine("Sound Alarm")
playBellSound()
} }
}) })
@@ -811,6 +958,8 @@ class TerminalViewModel(application: Application) : AndroidViewModel(application
_cols.value = model.defaultCols _cols.value = model.defaultCols
_screenBuffer.value = newClient.screenBuffer _screenBuffer.value = newClient.screenBuffer
applyModeSettings()
newClient.connect() newClient.connect()
} catch (e: Throwable) { } catch (e: Throwable) {
log.severe("Failed to connect: ${e.message}") log.severe("Failed to connect: ${e.message}")
@@ -29,6 +29,19 @@ object AppSettings {
private const val KEY_DYNAMIC_ROWS = "dynamic_rows" private const val KEY_DYNAMIC_ROWS = "dynamic_rows"
private const val KEY_DYNAMIC_COLS = "dynamic_cols" private const val KEY_DYNAMIC_COLS = "dynamic_cols"
// Entry Assist & Modes aligned with j3270
private const val KEY_ENTRY_ASSIST_DOC_MODE = "entry_assist_doc_mode"
private const val KEY_ENTRY_ASSIST_WORD_WRAP = "entry_assist_word_wrap"
private const val KEY_ENTRY_ASSIST_START_COL = "entry_assist_start_col"
private const val KEY_ENTRY_ASSIST_END_COL = "entry_assist_end_col"
private const val KEY_ENTRY_ASSIST_BELL = "entry_assist_bell"
private const val KEY_ENTRY_ASSIST_BELL_COL = "entry_assist_bell_col"
private const val KEY_ENTRY_ASSIST_TAB_STOPS = "entry_assist_tab_stops"
private const val KEY_INSERT_OFF_ON_AID = "insert_off_on_aid"
private const val KEY_FOUR_COLOR_OVERRIDE = "four_color_override"
private const val KEY_NUMERIC_FIELD_LOCK = "numeric_field_lock"
private const val KEY_AUTO_SKIP_ENABLED = "auto_skip_enabled"
enum class StartupBehavior { enum class StartupBehavior {
SHOW_CONNECT, SHOW_CONNECT,
DO_NOTHING, DO_NOTHING,
@@ -155,6 +168,111 @@ object AppSettings {
getPrefs(context).edit().putInt(KEY_DYNAMIC_COLS, cols.coerceIn(80, 255)).apply() getPrefs(context).edit().putInt(KEY_DYNAMIC_COLS, cols.coerceIn(80, 255)).apply()
} }
// ========== Entry Assist (Document Mode) ==========
fun getEntryAssistDocMode(context: Context): Boolean {
return getPrefs(context).getBoolean(KEY_ENTRY_ASSIST_DOC_MODE, false)
}
fun setEntryAssistDocMode(context: Context, enabled: Boolean) {
getPrefs(context).edit().putBoolean(KEY_ENTRY_ASSIST_DOC_MODE, enabled).apply()
}
fun getEntryAssistWordWrap(context: Context): Boolean {
return getPrefs(context).getBoolean(KEY_ENTRY_ASSIST_WORD_WRAP, false)
}
fun setEntryAssistWordWrap(context: Context, enabled: Boolean) {
getPrefs(context).edit().putBoolean(KEY_ENTRY_ASSIST_WORD_WRAP, enabled).apply()
}
fun getEntryAssistStartCol(context: Context): Int {
return getPrefs(context).getInt(KEY_ENTRY_ASSIST_START_COL, 1).coerceIn(1, 80)
}
fun setEntryAssistStartCol(context: Context, col: Int) {
getPrefs(context).edit().putInt(KEY_ENTRY_ASSIST_START_COL, col.coerceIn(1, 80)).apply()
}
fun getEntryAssistEndCol(context: Context): Int {
return getPrefs(context).getInt(KEY_ENTRY_ASSIST_END_COL, 80).coerceIn(1, 80)
}
fun setEntryAssistEndCol(context: Context, col: Int) {
getPrefs(context).edit().putInt(KEY_ENTRY_ASSIST_END_COL, col.coerceIn(1, 80)).apply()
}
fun getEntryAssistBell(context: Context): Boolean {
return getPrefs(context).getBoolean(KEY_ENTRY_ASSIST_BELL, false)
}
fun setEntryAssistBell(context: Context, enabled: Boolean) {
getPrefs(context).edit().putBoolean(KEY_ENTRY_ASSIST_BELL, enabled).apply()
}
fun getEntryAssistBellCol(context: Context): Int {
return getPrefs(context).getInt(KEY_ENTRY_ASSIST_BELL_COL, 75).coerceIn(1, 80)
}
fun setEntryAssistBellCol(context: Context, col: Int) {
getPrefs(context).edit().putInt(KEY_ENTRY_ASSIST_BELL_COL, col.coerceIn(1, 80)).apply()
}
fun getEntryAssistTabStops(context: Context): String {
return getPrefs(context).getString(KEY_ENTRY_ASSIST_TAB_STOPS, "5,10,15,20,25,30,35,40,45,50,55,60,65,70,75")
?: "5,10,15,20,25,30,35,40,45,50,55,60,65,70,75"
}
fun setEntryAssistTabStops(context: Context, stops: String) {
getPrefs(context).edit().putString(KEY_ENTRY_ASSIST_TAB_STOPS, stops).apply()
}
fun getEntryAssistTabStopsArray(context: Context): IntArray {
val s = getEntryAssistTabStops(context)
if (s.isBlank()) return intArrayOf()
val parts = s.split(",")
val list = mutableListOf<Int>()
for (p in parts) {
val v = p.trim().toIntOrNull()
if (v != null && v > 0) {
list.add(v - 1)
}
}
return list.toIntArray()
}
// ========== Terminal Operational Modes ==========
fun getInsertOffOnAid(context: Context): Boolean {
return getPrefs(context).getBoolean(KEY_INSERT_OFF_ON_AID, true)
}
fun setInsertOffOnAid(context: Context, enabled: Boolean) {
getPrefs(context).edit().putBoolean(KEY_INSERT_OFF_ON_AID, enabled).apply()
}
fun getFourColorOverride(context: Context): Boolean {
return getPrefs(context).getBoolean(KEY_FOUR_COLOR_OVERRIDE, false)
}
fun setFourColorOverride(context: Context, enabled: Boolean) {
getPrefs(context).edit().putBoolean(KEY_FOUR_COLOR_OVERRIDE, enabled).apply()
}
fun getNumericFieldLock(context: Context): Boolean {
return getPrefs(context).getBoolean(KEY_NUMERIC_FIELD_LOCK, true)
}
fun setNumericFieldLock(context: Context, enabled: Boolean) {
getPrefs(context).edit().putBoolean(KEY_NUMERIC_FIELD_LOCK, enabled).apply()
}
fun getAutoSkipEnabled(context: Context): Boolean {
return getPrefs(context).getBoolean(KEY_AUTO_SKIP_ENABLED, true)
}
fun setAutoSkipEnabled(context: Context, enabled: Boolean) {
getPrefs(context).edit().putBoolean(KEY_AUTO_SKIP_ENABLED, enabled).apply()
}
// ========== INI Config Export / Import ========== // ========== INI Config Export / Import ==========
fun exportToIni(context: Context, writer: Writer) { fun exportToIni(context: Context, writer: Writer) {
@@ -186,6 +304,23 @@ object AppSettings {
p.println("blockSelectMode = ${isBlockSelectModeEnabled(context)}") p.println("blockSelectMode = ${isBlockSelectModeEnabled(context)}")
p.println() p.println()
p.println("[entryassist]")
p.println("docMode = ${getEntryAssistDocMode(context)}")
p.println("wordWrap = ${getEntryAssistWordWrap(context)}")
p.println("startCol = ${getEntryAssistStartCol(context)}")
p.println("endCol = ${getEntryAssistEndCol(context)}")
p.println("bell = ${getEntryAssistBell(context)}")
p.println("bellCol = ${getEntryAssistBellCol(context)}")
p.println("tabStops = ${getEntryAssistTabStops(context)}")
p.println()
p.println("[modes]")
p.println("insertOffOnAid = ${getInsertOffOnAid(context)}")
p.println("fourColorOverride = ${getFourColorOverride(context)}")
p.println("numericFieldLock = ${getNumericFieldLock(context)}")
p.println("autoSkip = ${getAutoSkipEnabled(context)}")
p.println()
p.println("[graphics]") p.println("[graphics]")
p.println("graphicsMode = ${getDefaultGraphicsMode(context)}") p.println("graphicsMode = ${getDefaultGraphicsMode(context)}")
p.println() p.println()
@@ -236,6 +371,25 @@ object AppSettings {
"blockselectmode" -> setBlockSelectModeEnabled(context, value.toBoolean()) "blockselectmode" -> setBlockSelectModeEnabled(context, value.toBoolean())
} }
} }
"entryassist" -> {
when (key.lowercase(Locale.US)) {
"docmode", "entryassistdocmode" -> setEntryAssistDocMode(context, value.toBoolean())
"wordwrap", "entryassistwordwrap" -> setEntryAssistWordWrap(context, value.toBoolean())
"startcol", "entryassiststartcol" -> value.toIntOrNull()?.let { setEntryAssistStartCol(context, it) }
"endcol", "entryassistendcol" -> value.toIntOrNull()?.let { setEntryAssistEndCol(context, it) }
"bell", "entryassistbell" -> setEntryAssistBell(context, value.toBoolean())
"bellcol", "entryassistbellcol" -> value.toIntOrNull()?.let { setEntryAssistBellCol(context, it) }
"tabstops", "entryassisttabstops" -> setEntryAssistTabStops(context, value)
}
}
"modes" -> {
when (key.lowercase(Locale.US)) {
"insertoffonaid" -> setInsertOffOnAid(context, value.toBoolean())
"fourcoloroverride" -> setFourColorOverride(context, value.toBoolean())
"numericfieldlock" -> setNumericFieldLock(context, value.toBoolean())
"autoskip", "autoskipenabled" -> setAutoSkipEnabled(context, value.toBoolean())
}
}
"graphics" -> { "graphics" -> {
if (key.equals("graphicsmode", ignoreCase = true) || key.equals("mode", ignoreCase = true)) { if (key.equals("graphicsmode", ignoreCase = true) || key.equals("mode", ignoreCase = true)) {
setDefaultGraphicsMode(context, value.uppercase(Locale.US)) setDefaultGraphicsMode(context, value.uppercase(Locale.US))
@@ -37,6 +37,20 @@ private val TerminalKeyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Ascii keyboardType = KeyboardType.Ascii
) )
data class LastTransferState(
val hostFile: String = "",
val localFile: String = "",
val isReceive: Boolean = true,
val isAscii: Boolean = true,
val hostType: FTConfig.HostType = FTConfig.HostType.TSO,
val recfm: FTConfig.RecordFormat = FTConfig.RecordFormat.DEFAULT,
val lreclStr: String = "",
val blksizeStr: String = "",
val spaceStr: String = "",
val cmsOptions: String = ""
)
private var lastTransferState: LastTransferState? = null
@Composable @Composable
fun FileTransferDialog( fun FileTransferDialog(
initialHostType: String = "TSO", initialHostType: String = "TSO",
@@ -54,15 +68,17 @@ fun FileTransferDialog(
else -> FTConfig.HostType.TSO else -> FTConfig.HostType.TSO
} }
var hostFile by remember { mutableStateOf("") } var hostFile by remember { mutableStateOf(lastTransferState?.hostFile ?: "") }
var localFile by remember { mutableStateOf("") } var localFile by remember { mutableStateOf(lastTransferState?.localFile ?: "") }
var localDisplayName by remember { mutableStateOf("") } var localDisplayName by remember { mutableStateOf("") }
var isReceive by remember { mutableStateOf(true) } var isReceive by remember { mutableStateOf(lastTransferState?.isReceive ?: true) }
var isAscii by remember { mutableStateOf(true) } var isAscii by remember { mutableStateOf(lastTransferState?.isAscii ?: true) }
var hostType by remember { mutableStateOf(initialTypeEnum) } var hostType by remember { mutableStateOf(lastTransferState?.hostType ?: initialTypeEnum) }
var recfm by remember { mutableStateOf(FTConfig.RecordFormat.DEFAULT) } var recfm by remember { mutableStateOf(lastTransferState?.recfm ?: FTConfig.RecordFormat.DEFAULT) }
var lreclStr by remember { mutableStateOf("") } var lreclStr by remember { mutableStateOf(lastTransferState?.lreclStr ?: "") }
var blksizeStr by remember { mutableStateOf("") } var blksizeStr by remember { mutableStateOf(lastTransferState?.blksizeStr ?: "") }
var spaceStr by remember { mutableStateOf(lastTransferState?.spaceStr ?: "") }
var cmsOptions by remember { mutableStateOf(lastTransferState?.cmsOptions ?: "") }
var overwrite by remember { mutableStateOf(true) } var overwrite by remember { mutableStateOf(true) }
var showBrowseDialog by remember { mutableStateOf(false) } var showBrowseDialog by remember { mutableStateOf(false) }
@@ -467,10 +483,35 @@ fun FileTransferDialog(
onValueChange = { blksizeStr = it.filter { c -> c.isDigit() } }, onValueChange = { blksizeStr = it.filter { c -> c.isDigit() } },
label = { Text("BLKSIZE", fontSize = 11.sp) }, label = { Text("BLKSIZE", fontSize = 11.sp) },
singleLine = true, singleLine = true,
keyboardOptions = TerminalKeyboardOptions.copy(imeAction = ImeAction.Done), keyboardOptions = TerminalKeyboardOptions.copy(imeAction = ImeAction.Next),
modifier = Modifier.weight(1f) modifier = Modifier.weight(1f)
) )
} }
Spacer(modifier = Modifier.height(4.dp))
OutlinedTextField(
value = spaceStr,
onValueChange = { spaceStr = it },
label = { Text("SPACE (Allocation)", fontSize = 11.sp) },
placeholder = { Text("e.g. (1,1) cyl or 5,2") },
singleLine = true,
keyboardOptions = TerminalKeyboardOptions.copy(imeAction = ImeAction.Done),
modifier = Modifier.fillMaxWidth()
)
}
// CMS Options
if (hostType == FTConfig.HostType.CMS) {
Spacer(modifier = Modifier.height(4.dp))
OutlinedTextField(
value = cmsOptions,
onValueChange = { cmsOptions = it },
label = { Text("CMS Options", fontSize = 11.sp) },
placeholder = { Text("e.g. ASCII CRLF or RECFM F LRECL 80") },
singleLine = true,
keyboardOptions = TerminalKeyboardOptions.copy(imeAction = ImeAction.Done),
modifier = Modifier.fillMaxWidth()
)
} }
} }
}, },
@@ -497,8 +538,24 @@ fun FileTransferDialog(
setRecfm(recfm) setRecfm(recfm)
lreclStr.trim().toIntOrNull()?.let { setLrecl(it) } lreclStr.trim().toIntOrNull()?.let { setLrecl(it) }
blksizeStr.trim().toIntOrNull()?.let { setBlksize(it) } blksizeStr.trim().toIntOrNull()?.let { setBlksize(it) }
if (spaceStr.isNotBlank()) setSpace(spaceStr.trim())
}
if (hostType == FTConfig.HostType.CMS && cmsOptions.isNotBlank()) {
setOptions(cmsOptions.trim())
} }
} }
lastTransferState = LastTransferState(
hostFile = hostFile.trim(),
localFile = resolvedLocal,
isReceive = isReceive,
isAscii = isAscii,
hostType = hostType,
recfm = recfm,
lreclStr = lreclStr,
blksizeStr = blksizeStr,
spaceStr = spaceStr,
cmsOptions = cmsOptions
)
onStartTransfer(config) onStartTransfer(config)
} }
}, },
@@ -23,6 +23,11 @@ fun TwoRowKeyBar(
connectionState: ConnectionState, connectionState: ConnectionState,
isShiftPressed: Boolean = false, isShiftPressed: Boolean = false,
isLightPenMode: Boolean = false, isLightPenMode: Boolean = false,
isInsertMode: Boolean = false,
isDocMode: Boolean = false,
isWordWrap: Boolean = false,
isAplMode: Boolean = false,
fourColorOverride: Boolean = false,
hapticFeedbackEnabled: Boolean = true, hapticFeedbackEnabled: Boolean = true,
onClearShift: () -> Unit = {}, onClearShift: () -> Unit = {},
onConnectClick: () -> Unit, onConnectClick: () -> Unit,
@@ -34,6 +39,11 @@ fun TwoRowKeyBar(
onScriptClick: () -> Unit = {}, onScriptClick: () -> Unit = {},
onPrinterSessionClick: () -> Unit = {}, onPrinterSessionClick: () -> Unit = {},
onToggleLightPen: () -> Unit = {}, onToggleLightPen: () -> Unit = {},
onToggleInsert: () -> Unit = {},
onToggleDocMode: () -> Unit = {},
onToggleWordWrap: () -> Unit = {},
onToggleAplMode: () -> Unit = {},
onToggleFourColorOverride: () -> Unit = {},
onSettingsClick: () -> Unit = {}, onSettingsClick: () -> Unit = {},
onSendAid: (Int) -> Unit, onSendAid: (Int) -> Unit,
onReset: () -> Unit, onReset: () -> Unit,
@@ -178,6 +188,42 @@ fun TwoRowKeyBar(
} }
) )
HorizontalDivider(color = Color(0xFF3E4044)) HorizontalDivider(color = Color(0xFF3E4044))
DropdownMenuItem(
text = { Text("Insert Mode: ${if (isInsertMode) "ON" else "OFF"}", color = if (isInsertMode) Color(0xFF7890F0) else Color.White) },
onClick = {
menuExpanded = false
onToggleInsert()
}
)
DropdownMenuItem(
text = { Text("Document Mode (DOC): ${if (isDocMode) "ON" else "OFF"}", color = if (isDocMode) Color(0xFF38D9A9) else Color.White) },
onClick = {
menuExpanded = false
onToggleDocMode()
}
)
DropdownMenuItem(
text = { Text("Word Wrap: ${if (isWordWrap) "ON" else "OFF"}", color = if (isWordWrap) Color(0xFF38D9A9) else Color.White) },
onClick = {
menuExpanded = false
onToggleWordWrap()
}
)
DropdownMenuItem(
text = { Text("APL Keyboard Mode: ${if (isAplMode) "ON" else "OFF"}", color = if (isAplMode) Color(0xFFFFA94D) else Color.White) },
onClick = {
menuExpanded = false
onToggleAplMode()
}
)
DropdownMenuItem(
text = { Text("Base 4-Color Override: ${if (fourColorOverride) "ON" else "OFF"}", color = if (fourColorOverride) Color(0xFFFFD43B) else Color.White) },
onClick = {
menuExpanded = false
onToggleFourColorOverride()
}
)
HorizontalDivider(color = Color(0xFF3E4044))
} }
DropdownMenuItem( DropdownMenuItem(
text = { text = {
@@ -1,6 +1,7 @@
package haus.nightmare.a3270.ui package haus.nightmare.a3270.ui
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
@@ -27,13 +28,18 @@ fun OiaStatusBar(
isTls: Boolean = false, isTls: Boolean = false,
isTlsVerified: Boolean = true, isTlsVerified: Boolean = true,
graphicsMode: String = "NONE", graphicsMode: String = "NONE",
codePage: String = "037",
isLightPenMode: Boolean = false, isLightPenMode: Boolean = false,
isInsertMode: Boolean = false, isInsertMode: Boolean = false,
isDocMode: Boolean = false,
isWordWrap: Boolean = false,
isAplMode: Boolean = false,
onToggleInsert: (() -> Unit)? = null,
onToggleDocMode: (() -> Unit)? = null,
onToggleWordWrap: (() -> Unit)? = null,
onToggleAplMode: (() -> Unit)? = null,
inhibitReason: Int = 0, inhibitReason: Int = 0,
luName: String = "", luName: String = "",
isNumericField: Boolean = false, isNumericField: Boolean = false,
modelName: String = "",
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
val row = if (cols > 0 && rows > 0) ((cursorAddr / cols) % rows) + 1 else 1 val row = if (cols > 0 && rows > 0) ((cursorAddr / cols) % rows) + 1 else 1
@@ -73,8 +79,6 @@ fun OiaStatusBar(
txt to clr txt to clr
} else if (isKeyboardLocked) { } else if (isKeyboardLocked) {
"X SYSTEM" to Color(0xFFFFFFFF) "X SYSTEM" to Color(0xFFFFFFFF)
} else if (isInsertMode) {
"INSERT" to Color(0xFF7890F0)
} else { } else {
"READY" to Color(0xFF7890F0) "READY" to Color(0xFF7890F0)
} }
@@ -148,24 +152,6 @@ fun OiaStatusBar(
} }
} }
// CodePage Badge
if (connectionState.isConnected() && codePage.isNotBlank()) {
Spacer(modifier = Modifier.width(6.dp))
Surface(
color = Color(0xFFFA5252).copy(alpha = 0.2f),
shape = RoundedCornerShape(4.dp)
) {
Text(
text = "CP$codePage",
color = Color(0xFFFF8787),
fontSize = 9.sp,
fontFamily = FontFamily.Monospace,
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(horizontal = 4.dp, vertical = 1.dp)
)
}
}
// Field Status: NUM vs ALPHA // Field Status: NUM vs ALPHA
if (connectionState.isConnected() && rows > 0 && cols > 0) { if (connectionState.isConnected() && rows > 0 && cols > 0) {
Spacer(modifier = Modifier.width(6.dp)) Spacer(modifier = Modifier.width(6.dp))
@@ -184,24 +170,6 @@ fun OiaStatusBar(
} }
} }
// Model / Dimensions Badge
if (connectionState.isConnected() && rows > 0 && cols > 0) {
Spacer(modifier = Modifier.width(6.dp))
Surface(
color = Color(0xFF495057).copy(alpha = 0.3f),
shape = RoundedCornerShape(4.dp)
) {
Text(
text = if (modelName.isNotBlank()) modelName else "${rows}x${cols}",
color = Color(0xFFCED4DA),
fontSize = 9.sp,
fontFamily = FontFamily.Monospace,
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(horizontal = 4.dp, vertical = 1.dp)
)
}
}
// Light Pen Badge // Light Pen Badge
if (isLightPenMode) { if (isLightPenMode) {
Spacer(modifier = Modifier.width(6.dp)) Spacer(modifier = Modifier.width(6.dp))
@@ -257,6 +225,78 @@ fun OiaStatusBar(
} }
} }
// Operational Mode Badges (^ INS, APL, DOC, V)
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(4.dp),
modifier = Modifier.padding(horizontal = 2.dp)
) {
if (connectionState.isConnected() && isInsertMode) {
Surface(
color = Color(0xFF7890F0).copy(alpha = 0.2f),
shape = RoundedCornerShape(4.dp),
modifier = Modifier.then(if (onToggleInsert != null) Modifier.clickable { onToggleInsert() } else Modifier)
) {
Text(
text = "^ INS",
color = Color(0xFF7890F0),
fontSize = 10.sp,
fontFamily = FontFamily.Monospace,
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(horizontal = 4.dp, vertical = 2.dp)
)
}
}
if (connectionState.isConnected() && isAplMode) {
Surface(
color = Color(0xFFFF922B).copy(alpha = 0.2f),
shape = RoundedCornerShape(4.dp),
modifier = Modifier.then(if (onToggleAplMode != null) Modifier.clickable { onToggleAplMode() } else Modifier)
) {
Text(
text = "APL",
color = Color(0xFFFFA94D),
fontSize = 10.sp,
fontFamily = FontFamily.Monospace,
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(horizontal = 4.dp, vertical = 2.dp)
)
}
}
if (connectionState.isConnected() && isDocMode) {
Surface(
color = Color(0xFF20C997).copy(alpha = 0.2f),
shape = RoundedCornerShape(4.dp),
modifier = Modifier.then(if (onToggleDocMode != null) Modifier.clickable { onToggleDocMode() } else Modifier)
) {
Text(
text = "DOC",
color = Color(0xFF38D9A9),
fontSize = 10.sp,
fontFamily = FontFamily.Monospace,
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(horizontal = 4.dp, vertical = 2.dp)
)
}
}
if (connectionState.isConnected() && isWordWrap) {
Surface(
color = Color(0xFF20C997).copy(alpha = 0.2f),
shape = RoundedCornerShape(4.dp),
modifier = Modifier.then(if (onToggleWordWrap != null) Modifier.clickable { onToggleWordWrap() } else Modifier)
) {
Text(
text = "V",
color = Color(0xFF38D9A9),
fontSize = 10.sp,
fontFamily = FontFamily.Monospace,
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(horizontal = 4.dp, vertical = 2.dp)
)
}
}
}
// Terminal Keyboard Lock Status Badge (X SYSTEM vs READY) // Terminal Keyboard Lock Status Badge (X SYSTEM vs READY)
Surface( Surface(
color = lockStatusColor.copy(alpha = 0.2f), color = lockStatusColor.copy(alpha = 0.2f),
@@ -33,6 +33,17 @@ fun SettingsDialog(
initialStartupBehavior: String = "SHOW_CONNECT", initialStartupBehavior: String = "SHOW_CONNECT",
initialDynamicRows: Int = 62, initialDynamicRows: Int = 62,
initialDynamicCols: Int = 160, initialDynamicCols: Int = 160,
initialEntryAssistDocMode: Boolean = false,
initialEntryAssistWordWrap: Boolean = false,
initialEntryAssistStartCol: Int = 1,
initialEntryAssistEndCol: Int = 80,
initialEntryAssistBell: Boolean = false,
initialEntryAssistBellCol: Int = 75,
initialEntryAssistTabStops: String = "5,10,15,20,25,30,35,40,45,50,55,60,65,70,75",
initialInsertOffOnAid: Boolean = true,
initialFourColorOverride: Boolean = false,
initialNumericFieldLock: Boolean = true,
initialAutoSkipEnabled: Boolean = true,
onDismiss: () -> Unit, onDismiss: () -> Unit,
onSave: ( onSave: (
maskHiddenInput: Boolean, maskHiddenInput: Boolean,
@@ -47,7 +58,18 @@ fun SettingsDialog(
blockSelectMode: Boolean, blockSelectMode: Boolean,
startupBehavior: String, startupBehavior: String,
dynamicRows: Int, dynamicRows: Int,
dynamicCols: Int dynamicCols: Int,
entryAssistDocMode: Boolean,
entryAssistWordWrap: Boolean,
entryAssistStartCol: Int,
entryAssistEndCol: Int,
entryAssistBell: Boolean,
entryAssistBellCol: Int,
entryAssistTabStops: String,
insertOffOnAid: Boolean,
fourColorOverride: Boolean,
numericFieldLock: Boolean,
autoSkipEnabled: Boolean
) -> Unit ) -> Unit
) { ) {
val context = androidx.compose.ui.platform.LocalContext.current val context = androidx.compose.ui.platform.LocalContext.current
@@ -68,6 +90,18 @@ fun SettingsDialog(
var dynamicRowsStr by remember { mutableStateOf(initialDynamicRows.toString()) } var dynamicRowsStr by remember { mutableStateOf(initialDynamicRows.toString()) }
var dynamicColsStr by remember { mutableStateOf(initialDynamicCols.toString()) } var dynamicColsStr by remember { mutableStateOf(initialDynamicCols.toString()) }
var entryAssistDocMode by remember { mutableStateOf(initialEntryAssistDocMode) }
var entryAssistWordWrap by remember { mutableStateOf(initialEntryAssistWordWrap) }
var entryAssistStartColStr by remember { mutableStateOf(initialEntryAssistStartCol.toString()) }
var entryAssistEndColStr by remember { mutableStateOf(initialEntryAssistEndCol.toString()) }
var entryAssistBell by remember { mutableStateOf(initialEntryAssistBell) }
var entryAssistBellColStr by remember { mutableStateOf(initialEntryAssistBellCol.toString()) }
var entryAssistTabStops by remember { mutableStateOf(initialEntryAssistTabStops) }
var insertOffOnAid by remember { mutableStateOf(initialInsertOffOnAid) }
var fourColorOverride by remember { mutableStateOf(initialFourColorOverride) }
var numericFieldLock by remember { mutableStateOf(initialNumericFieldLock) }
var autoSkipEnabled by remember { mutableStateOf(initialAutoSkipEnabled) }
var showImportDialog by remember { mutableStateOf(false) } var showImportDialog by remember { mutableStateOf(false) }
Dialog( Dialog(
@@ -416,7 +450,207 @@ fun SettingsDialog(
HorizontalDivider(color = Color(0xFF2C2D30)) HorizontalDivider(color = Color(0xFF2C2D30))
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
// SECTION 4: INI CONFIGURATION BACKUP // SECTION 4: ENTRY ASSIST & MODES
Text("Entry Assist & Modes", fontSize = 13.sp, fontWeight = FontWeight.Bold, color = Color(0xFF339AF0))
Text("Document Mode word wrap, margins, audible bell, and operational locks matching j3270.", fontSize = 12.sp, color = Color.LightGray)
Spacer(modifier = Modifier.height(8.dp))
// Document Mode
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Column(modifier = Modifier.weight(1f).padding(end = 12.dp)) {
Text("Document Mode (DOC)", fontSize = 14.sp, fontWeight = FontWeight.SemiBold, color = Color.White)
Text("Enable word wrapping and margin tracking during text entry.", fontSize = 12.sp, color = Color.LightGray)
}
Switch(
checked = entryAssistDocMode,
onCheckedChange = { entryAssistDocMode = it },
colors = SwitchDefaults.colors(checkedThumbColor = Color.White, checkedTrackColor = Color(0xFF2B8A3E))
)
}
Spacer(modifier = Modifier.height(10.dp))
// Word Wrap
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Column(modifier = Modifier.weight(1f).padding(end = 12.dp)) {
Text("Word Wrap (V)", fontSize = 14.sp, fontWeight = FontWeight.SemiBold, color = Color.White)
Text("Automatically wrap partial words at the right margin.", fontSize = 12.sp, color = Color.LightGray)
}
Switch(
checked = entryAssistWordWrap,
onCheckedChange = { entryAssistWordWrap = it },
colors = SwitchDefaults.colors(checkedThumbColor = Color.White, checkedTrackColor = Color(0xFF2B8A3E))
)
}
Spacer(modifier = Modifier.height(10.dp))
// Margins (Left / Right)
Column(modifier = Modifier.fillMaxWidth()) {
Text("Entry Assist Margins", fontSize = 14.sp, fontWeight = FontWeight.SemiBold, color = Color.White)
Text("Left margin (1-80) and Right margin (1-80).", fontSize = 12.sp, color = Color.LightGray)
Spacer(modifier = Modifier.height(6.dp))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
OutlinedTextField(
value = entryAssistStartColStr,
onValueChange = { entryAssistStartColStr = it },
label = { Text("Left Col (1-80)") },
singleLine = true,
modifier = Modifier.weight(1f),
keyboardOptions = androidx.compose.foundation.text.KeyboardOptions(
keyboardType = androidx.compose.ui.text.input.KeyboardType.Number
)
)
OutlinedTextField(
value = entryAssistEndColStr,
onValueChange = { entryAssistEndColStr = it },
label = { Text("Right Col (1-80)") },
singleLine = true,
modifier = Modifier.weight(1f),
keyboardOptions = androidx.compose.foundation.text.KeyboardOptions(
keyboardType = androidx.compose.ui.text.input.KeyboardType.Number
)
)
}
}
Spacer(modifier = Modifier.height(10.dp))
// Audible EOL Bell
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Column(modifier = Modifier.weight(1f).padding(end = 12.dp)) {
Text("Audible EOL Bell Warning", fontSize = 14.sp, fontWeight = FontWeight.SemiBold, color = Color.White)
Text("Beep warning signal when typing reaches the bell column.", fontSize = 12.sp, color = Color.LightGray)
}
Switch(
checked = entryAssistBell,
onCheckedChange = { entryAssistBell = it },
colors = SwitchDefaults.colors(checkedThumbColor = Color.White, checkedTrackColor = Color(0xFF2B8A3E))
)
}
Spacer(modifier = Modifier.height(6.dp))
OutlinedTextField(
value = entryAssistBellColStr,
onValueChange = { entryAssistBellColStr = it },
label = { Text("Bell Column (1-80)") },
singleLine = true,
modifier = Modifier.fillMaxWidth(),
keyboardOptions = androidx.compose.foundation.text.KeyboardOptions(
keyboardType = androidx.compose.ui.text.input.KeyboardType.Number
)
)
Spacer(modifier = Modifier.height(10.dp))
// Tab Stops
OutlinedTextField(
value = entryAssistTabStops,
onValueChange = { entryAssistTabStops = it },
label = { Text("Word Tab Stops") },
placeholder = { Text("5,10,15,20,25,30,35,40,45,50,55,60,65,70,75") },
singleLine = true,
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(10.dp))
// Operational Mode Toggles
// 1. Reset Insert on AID
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Column(modifier = Modifier.weight(1f).padding(end = 12.dp)) {
Text("Reset Insert Mode on AID Key", fontSize = 14.sp, fontWeight = FontWeight.SemiBold, color = Color.White)
Text("Automatically cancel insert mode upon Enter, PF, PA, or Clear.", fontSize = 12.sp, color = Color.LightGray)
}
Switch(
checked = insertOffOnAid,
onCheckedChange = { insertOffOnAid = it },
colors = SwitchDefaults.colors(checkedThumbColor = Color.White, checkedTrackColor = Color(0xFF2B8A3E))
)
}
Spacer(modifier = Modifier.height(10.dp))
// 2. Base 4-Color Override
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Column(modifier = Modifier.weight(1f).padding(end = 12.dp)) {
Text("Base 4-Color Override Mode", fontSize = 14.sp, fontWeight = FontWeight.SemiBold, color = Color.White)
Text("Force 3279 4-color mapping (green/white/red/turquoise), ignoring host extended colors.", fontSize = 12.sp, color = Color.LightGray)
}
Switch(
checked = fourColorOverride,
onCheckedChange = { fourColorOverride = it },
colors = SwitchDefaults.colors(checkedThumbColor = Color.White, checkedTrackColor = Color(0xFF2B8A3E))
)
}
Spacer(modifier = Modifier.height(10.dp))
// 3. Numeric Field Lock
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Column(modifier = Modifier.weight(1f).padding(end = 12.dp)) {
Text("Numeric Field Lock (-NUMERIC)", fontSize = 14.sp, fontWeight = FontWeight.SemiBold, color = Color.White)
Text("Inhibit keyboard if a non-numeric character is typed in a numeric-only field.", fontSize = 12.sp, color = Color.LightGray)
}
Switch(
checked = numericFieldLock,
onCheckedChange = { numericFieldLock = it },
colors = SwitchDefaults.colors(checkedThumbColor = Color.White, checkedTrackColor = Color(0xFF2B8A3E))
)
}
Spacer(modifier = Modifier.height(10.dp))
// 4. Auto-Skip
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Column(modifier = Modifier.weight(1f).padding(end = 12.dp)) {
Text("Auto-Skip Across Fields", fontSize = 14.sp, fontWeight = FontWeight.SemiBold, color = Color.White)
Text("Automatically advance cursor to next unprotected field when field is filled.", fontSize = 12.sp, color = Color.LightGray)
}
Switch(
checked = autoSkipEnabled,
onCheckedChange = { autoSkipEnabled = it },
colors = SwitchDefaults.colors(checkedThumbColor = Color.White, checkedTrackColor = Color(0xFF2B8A3E))
)
}
Spacer(modifier = Modifier.height(16.dp))
HorizontalDivider(color = Color(0xFF2C2D30))
Spacer(modifier = Modifier.height(16.dp))
// SECTION 5: INI CONFIGURATION BACKUP
Text("Configuration File (INI)", fontSize = 13.sp, fontWeight = FontWeight.Bold, color = Color(0xFF339AF0)) Text("Configuration File (INI)", fontSize = 13.sp, fontWeight = FontWeight.Bold, color = Color(0xFF339AF0))
Text("Compatible with j3270 desktop INI configuration files.", fontSize = 12.sp, color = Color.LightGray) Text("Compatible with j3270 desktop INI configuration files.", fontSize = 12.sp, color = Color.LightGray)
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
@@ -459,6 +693,9 @@ fun SettingsDialog(
onClick = { onClick = {
val dRows = dynamicRowsStr.trim().toIntOrNull()?.coerceIn(24, 255) ?: 62 val dRows = dynamicRowsStr.trim().toIntOrNull()?.coerceIn(24, 255) ?: 62
val dCols = dynamicColsStr.trim().toIntOrNull()?.coerceIn(80, 255) ?: 160 val dCols = dynamicColsStr.trim().toIntOrNull()?.coerceIn(80, 255) ?: 160
val eaStartCol = entryAssistStartColStr.trim().toIntOrNull()?.coerceIn(1, 80) ?: 1
val eaEndCol = entryAssistEndColStr.trim().toIntOrNull()?.coerceIn(1, 80) ?: 80
val eaBellCol = entryAssistBellColStr.trim().toIntOrNull()?.coerceIn(1, 80) ?: 75
onSave( onSave(
maskHiddenInput, maskHiddenInput,
cursorBlink, cursorBlink,
@@ -472,7 +709,18 @@ fun SettingsDialog(
blockSelectMode, blockSelectMode,
startupBehavior, startupBehavior,
dRows, dRows,
dCols dCols,
entryAssistDocMode,
entryAssistWordWrap,
eaStartCol,
eaEndCol,
entryAssistBell,
eaBellCol,
entryAssistTabStops,
insertOffOnAid,
fourColorOverride,
numericFieldLock,
autoSkipEnabled
) )
}, },
colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF2B8A3E)) colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF2B8A3E))
@@ -517,6 +765,17 @@ fun SettingsDialog(
dynamicColsStr = haus.nightmare.a3270.storage.AppSettings.getDynamicCols(context).toString() dynamicColsStr = haus.nightmare.a3270.storage.AppSettings.getDynamicCols(context).toString()
defaultCodePage = haus.nightmare.a3270.storage.AppSettings.getDefaultCodePage(context) defaultCodePage = haus.nightmare.a3270.storage.AppSettings.getDefaultCodePage(context)
defaultGraphicsMode = haus.nightmare.a3270.storage.AppSettings.getDefaultGraphicsMode(context) defaultGraphicsMode = haus.nightmare.a3270.storage.AppSettings.getDefaultGraphicsMode(context)
entryAssistDocMode = haus.nightmare.a3270.storage.AppSettings.getEntryAssistDocMode(context)
entryAssistWordWrap = haus.nightmare.a3270.storage.AppSettings.getEntryAssistWordWrap(context)
entryAssistStartColStr = haus.nightmare.a3270.storage.AppSettings.getEntryAssistStartCol(context).toString()
entryAssistEndColStr = haus.nightmare.a3270.storage.AppSettings.getEntryAssistEndCol(context).toString()
entryAssistBell = haus.nightmare.a3270.storage.AppSettings.getEntryAssistBell(context)
entryAssistBellColStr = haus.nightmare.a3270.storage.AppSettings.getEntryAssistBellCol(context).toString()
entryAssistTabStops = haus.nightmare.a3270.storage.AppSettings.getEntryAssistTabStops(context)
insertOffOnAid = haus.nightmare.a3270.storage.AppSettings.getInsertOffOnAid(context)
fourColorOverride = haus.nightmare.a3270.storage.AppSettings.getFourColorOverride(context)
numericFieldLock = haus.nightmare.a3270.storage.AppSettings.getNumericFieldLock(context)
autoSkipEnabled = haus.nightmare.a3270.storage.AppSettings.getAutoSkipEnabled(context)
android.widget.Toast.makeText(context, "Settings imported from INI", android.widget.Toast.LENGTH_SHORT).show() android.widget.Toast.makeText(context, "Settings imported from INI", android.widget.Toast.LENGTH_SHORT).show()
} catch (e: Exception) { } catch (e: Exception) {
android.widget.Toast.makeText(context, "Error importing: ${e.message}", android.widget.Toast.LENGTH_LONG).show() android.widget.Toast.makeText(context, "Error importing: ${e.message}", android.widget.Toast.LENGTH_LONG).show()
@@ -70,6 +70,7 @@ fun TerminalView(
cursorStyle: String = "BLOCK", cursorStyle: String = "BLOCK",
isInsertMode: Boolean = false, isInsertMode: Boolean = false,
blockSelectMode: Boolean = false, blockSelectMode: Boolean = false,
fourColorOverride: Boolean = false,
searchHighlightAddr: Int = -1, searchHighlightAddr: Int = -1,
searchHighlightLen: Int = 0, searchHighlightLen: Int = 0,
onTapAddress: (Int) -> Unit, onTapAddress: (Int) -> Unit,
@@ -320,7 +321,7 @@ fun TerminalView(
} }
// Compute 3270 color and field intensity // Compute 3270 color and field intensity
fgColor = getFgColorForAttribute(ea, currentFieldEa, currentFA) fgColor = getFgColorForAttribute(ea, currentFieldEa, currentFA, fourColorOverride)
bgColor = getBgColorForAttribute(ea, currentFieldEa) bgColor = getBgColorForAttribute(ea, currentFieldEa)
csVal = if (ea.cs != 0.toByte()) (ea.cs.toInt() and 0xFF) csVal = if (ea.cs != 0.toByte()) (ea.cs.toInt() and 0xFF)
@@ -728,13 +729,20 @@ private fun copySelection(
} }
} }
private fun getFgColorForAttribute(ea: ExtendedAttribute, currentFieldEa: ExtendedAttribute?, currentFA: Byte): Color { private fun getFgColorForAttribute(
val fg = if (ea.fg != 0.toByte()) (ea.fg.toInt() and 0xFF) ea: ExtendedAttribute,
else if (currentFieldEa != null && currentFieldEa.fg != 0.toByte()) (currentFieldEa.fg.toInt() and 0xFF) currentFieldEa: ExtendedAttribute?,
else 0 currentFA: Byte,
fourColorOverride: Boolean = false
): Color {
if (!fourColorOverride) {
val fg = if (ea.fg != 0.toByte()) (ea.fg.toInt() and 0xFF)
else if (currentFieldEa != null && currentFieldEa.fg != 0.toByte()) (currentFieldEa.fg.toInt() and 0xFF)
else 0
if (fg in 0xf0..0xff) { if (fg in 0xf0..0xff) {
return HOST_COLORS[fg - 0xf0] return HOST_COLORS[fg - 0xf0]
}
} }
val fa = currentFA.toInt() and 0xFF val fa = currentFA.toInt() and 0xFF
return if (faIsProtected(fa)) { return if (faIsProtected(fa)) {
@@ -0,0 +1,239 @@
package haus.nightmare.a3270
import haus.nightmare.lib3270j.TerminalModel
import haus.nightmare.lib3270j.charset.EbcdicTranslator
import haus.nightmare.lib3270j.ecl.ECLConstants
import haus.nightmare.lib3270j.ecl.ECLOIA
import haus.nightmare.lib3270j.input.InputProcessor
import haus.nightmare.lib3270j.protocol.DS3270Constants.*
import haus.nightmare.lib3270j.screen.ScreenBuffer
import org.junit.Assert.*
import org.junit.Test
import java.util.concurrent.atomic.AtomicInteger
class EntryAssistAndModesTest {
@Test
fun testEntryAssistDocModeAndWordWrap() {
val translator = EbcdicTranslator()
val screen = ScreenBuffer(TerminalModel.IBM_3279_2, translator)
val inputProcessor = InputProcessor(screen, translator, null)
val oia = ECLOIA(screen, inputProcessor, null)
inputProcessor.setOIA(oia)
screen.isEntryAssistDOCmode = true
screen.isEntryAssistWordWrap = true
screen.setLeftMargin(0)
screen.setRightMargin(20)
assertTrue(screen.isEntryAssistDOCmode)
assertTrue(screen.isEntryAssistWordWrap)
assertEquals(0, screen.entryAssistStartColumn)
assertEquals(20, screen.entryAssistEndColumn)
// Type at col 18 with word crossing col 20
screen.setCursorPosition(0, 18)
inputProcessor.typeCharacter('T')
inputProcessor.typeCharacter('E')
inputProcessor.typeCharacter('S')
inputProcessor.typeCharacter('T')
// Word wrap moves partial word to next row starting at left margin
assertEquals(1, screen.cursorRow)
assertTrue(screen.cursorCol >= 0)
}
@Test
fun testDocModeTabStops() {
val translator = EbcdicTranslator()
val screen = ScreenBuffer(TerminalModel.IBM_3279_2, translator)
screen.isEntryAssistDOCmode = true
screen.setLeftMargin(0)
screen.setRightMargin(79)
screen.setWordTabPositions(intArrayOf(10, 20, 30))
screen.setCursorPosition(0, 0)
screen.processWordTab(true)
assertEquals(10, screen.cursorCol)
screen.processWordTab(true)
assertEquals(20, screen.cursorCol)
screen.processWordTab(false)
assertEquals(10, screen.cursorCol)
}
@Test
fun testAplModeAndGraphicEscape() {
val translator = EbcdicTranslator()
val screen = ScreenBuffer(TerminalModel.IBM_3279_2, translator)
val inputProcessor = InputProcessor(screen, translator, null)
val oia = ECLOIA(screen, inputProcessor, null)
inputProcessor.setOIA(oia)
assertFalse(inputProcessor.isAplKeyboardMode)
inputProcessor.isAplKeyboardMode = true
assertTrue(inputProcessor.isAplKeyboardMode)
assertTrue(oia.isApl)
// Type 'a' in APL mode -> stores CS_GE Graphic Escape charset
screen.setCursorPosition(0, 0)
inputProcessor.typeCharacter('a')
val cell = screen.getCell(0)
assertEquals(CS_GE.toInt(), cell.cs.toInt())
// Toggle APL mode off
inputProcessor.toggleAplKeyboardMode()
assertFalse(inputProcessor.isAplKeyboardMode)
assertFalse(oia.isApl)
}
@Test
fun testAudibleBellCallback() {
val translator = EbcdicTranslator()
val screen = ScreenBuffer(TerminalModel.IBM_3279_2, translator)
val inputProcessor = InputProcessor(screen, translator, null)
val bellCount = AtomicInteger(0)
inputProcessor.setBellListener { bellCount.incrementAndGet() }
inputProcessor.isBellEnabled = true
inputProcessor.bellColumn = 74
screen.setCursorPosition(0, 70)
inputProcessor.typeCharacter('A')
inputProcessor.typeCharacter('B')
inputProcessor.typeCharacter('C')
assertEquals(0, bellCount.get())
inputProcessor.typeCharacter('D') // Col 74 -> Bell fires
assertEquals(1, bellCount.get())
inputProcessor.typeCharacter('E') // Same line -> No re-fire
assertEquals(1, bellCount.get())
}
@Test
fun testInsertModeResetOnAid() {
val translator = EbcdicTranslator()
val screen = ScreenBuffer(TerminalModel.IBM_3279_2, translator)
val inputProcessor = InputProcessor(screen, translator, null)
inputProcessor.isInsertOffOnAid = true
inputProcessor.isInsertMode = true
assertTrue(inputProcessor.isInsertMode)
inputProcessor.sendAid(AID_ENTER)
assertFalse(inputProcessor.isInsertMode)
// Disabled
inputProcessor.isInsertOffOnAid = false
inputProcessor.isInsertMode = true
inputProcessor.sendAid(AID_ENTER)
assertTrue(inputProcessor.isInsertMode)
}
@Test
fun testNumericFieldLock() {
val translator = EbcdicTranslator()
val screen = ScreenBuffer(TerminalModel.IBM_3279_2, translator)
val inputProcessor = InputProcessor(screen, translator, null)
val oia = ECLOIA(screen, inputProcessor, null)
inputProcessor.setOIA(oia)
screen.setFieldAttribute(0, FA_NUMERIC.toByte())
screen.setFieldAttribute(6, FA_PROTECT.toByte())
screen.cursorAddress = 1
inputProcessor.isNumericFieldLock = true
inputProcessor.typeCharacter('Z') // Non-numeric
assertTrue(inputProcessor.isKeyboardLocked)
assertEquals(ECLConstants.INHIBIT_NUMERIC_ONLY, oia.inputInhibited)
inputProcessor.reset()
assertFalse(inputProcessor.isKeyboardLocked)
// With numeric lock disabled
inputProcessor.isNumericFieldLock = false
screen.cursorAddress = 1
inputProcessor.typeCharacter('Z')
assertFalse(inputProcessor.isKeyboardLocked)
}
@Test
fun testBase4ColorParity() {
// In 3279 Base 4-Color Mode:
// Intensified Protected -> White (0xFFFFFF)
// Normal Protected -> Turquoise / Cyan (0x00FFFF)
// Intensified Unprotected -> Red (0xFF0000)
// Normal Unprotected -> Green (0x00FF00)
val faNormProt = FA_PROTECT
val faHighProt = FA_PROTECT or FA_INT_HIGH_SEL
val faNormUnprot = 0
val faHighUnprot = FA_INT_HIGH_SEL
fun getBase4Color(fa: Int): Int {
val isProt = (fa and FA_PROTECT) != 0
val isHigh = (fa and FA_INT_HIGH_SEL) != 0
return if (isProt) {
if (isHigh) 0xFFFFFF else 0x00FFFF
} else {
if (isHigh) 0xFF0000 else 0x00FF00
}
}
assertEquals(0xFFFFFF, getBase4Color(faHighProt))
assertEquals(0x00FFFF, getBase4Color(faNormProt))
assertEquals(0xFF0000, getBase4Color(faHighUnprot))
assertEquals(0x00FF00, getBase4Color(faNormUnprot))
}
@Test
fun testIniSectionSyntaxMatchingJ3270() {
val sampleIni = """
[entryassist]
docMode = true
wordWrap = true
startCol = 5
endCol = 75
bell = true
bellCol = 70
tabStops = 5,10,15,20
[modes]
resetInsertOnAid = true
fourColorOverride = true
numericLock = false
autoSkip = true
""".trimIndent()
val lines = sampleIni.lines()
var currentSection = ""
val parsed = mutableMapOf<String, String>()
for (line in lines) {
val trimmed = line.trim()
if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
currentSection = trimmed.substring(1, trimmed.length - 1).lowercase()
} else if (trimmed.contains("=")) {
val parts = trimmed.split("=", limit = 2)
val key = parts[0].trim().lowercase()
val value = parts[1].trim()
parsed["$currentSection.$key"] = value
}
}
assertEquals("true", parsed["entryassist.docmode"])
assertEquals("true", parsed["entryassist.wordwrap"])
assertEquals("5", parsed["entryassist.startcol"])
assertEquals("75", parsed["entryassist.endcol"])
assertEquals("true", parsed["entryassist.bell"])
assertEquals("70", parsed["entryassist.bellcol"])
assertEquals("5,10,15,20", parsed["entryassist.tabstops"])
assertEquals("true", parsed["modes.resetinsertonaid"])
assertEquals("true", parsed["modes.fourcoloroverride"])
assertEquals("false", parsed["modes.numericlock"])
assertEquals("true", parsed["modes.autoskip"])
}
}