Overnight churning adaptations
This commit is contained in:
@@ -215,6 +215,26 @@ class MainActivity : ComponentActivity() {
|
|||||||
viewModel.sendAid(AID_PA1)
|
viewModel.sendAid(AID_PA1)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
KeyEvent.KEYCODE_A -> {
|
||||||
|
viewModel.attn()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
KeyEvent.KEYCODE_S -> {
|
||||||
|
viewModel.sysReq()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
KeyEvent.KEYCODE_DPAD_LEFT -> {
|
||||||
|
viewModel.wordLeft()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
KeyEvent.KEYCODE_DPAD_RIGHT -> {
|
||||||
|
viewModel.wordRight()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
KeyEvent.KEYCODE_FORWARD_DEL, KeyEvent.KEYCODE_DEL -> {
|
||||||
|
viewModel.deleteWord()
|
||||||
|
return true
|
||||||
|
}
|
||||||
KeyEvent.KEYCODE_HOME -> {
|
KeyEvent.KEYCODE_HOME -> {
|
||||||
viewModel.cursorHome()
|
viewModel.cursorHome()
|
||||||
return true
|
return true
|
||||||
@@ -249,6 +269,14 @@ class MainActivity : ComponentActivity() {
|
|||||||
viewModel.resetKeyboard()
|
viewModel.resetKeyboard()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
KeyEvent.KEYCODE_D -> {
|
||||||
|
viewModel.deleteWord()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
KeyEvent.KEYCODE_E -> {
|
||||||
|
viewModel.fieldEnd()
|
||||||
|
return true
|
||||||
|
}
|
||||||
KeyEvent.KEYCODE_L -> {
|
KeyEvent.KEYCODE_L -> {
|
||||||
viewModel.toggleLightPen()
|
viewModel.toggleLightPen()
|
||||||
val isLp = viewModel.isLightPenMode.value
|
val isLp = viewModel.isLightPenMode.value
|
||||||
@@ -593,15 +621,15 @@ fun MainScreen(
|
|||||||
terminalInputViewRef?.showSoftKeyboard()
|
terminalInputViewRef?.showSoftKeyboard()
|
||||||
},
|
},
|
||||||
onAttn = {
|
onAttn = {
|
||||||
viewModel.sendAid(AID_PA1)
|
viewModel.attn()
|
||||||
terminalInputViewRef?.showSoftKeyboard()
|
terminalInputViewRef?.showSoftKeyboard()
|
||||||
},
|
},
|
||||||
onSysReq = {
|
onSysReq = {
|
||||||
viewModel.sendAid(AID_SYSREQ)
|
viewModel.sysReq()
|
||||||
terminalInputViewRef?.showSoftKeyboard()
|
terminalInputViewRef?.showSoftKeyboard()
|
||||||
},
|
},
|
||||||
onCursorSelect = {
|
onCursorSelect = {
|
||||||
viewModel.sendAid(AID_SELECT)
|
viewModel.cursorSelect()
|
||||||
terminalInputViewRef?.showSoftKeyboard()
|
terminalInputViewRef?.showSoftKeyboard()
|
||||||
},
|
},
|
||||||
onCursorLeft = {
|
onCursorLeft = {
|
||||||
@@ -635,7 +663,10 @@ fun MainScreen(
|
|||||||
isTlsVerified = isTlsVerified,
|
isTlsVerified = isTlsVerified,
|
||||||
graphicsMode = defaultGraphicsMode,
|
graphicsMode = defaultGraphicsMode,
|
||||||
codePage = codePage,
|
codePage = codePage,
|
||||||
isLightPenMode = isLightPenMode
|
isLightPenMode = isLightPenMode,
|
||||||
|
isInsertMode = viewModel.getClient()?.inputProcessor?.isInsertMode ?: false,
|
||||||
|
inhibitReason = viewModel.getClient()?.oia?.inputInhibited ?: 0,
|
||||||
|
luName = viewModel.getClient()?.telnetFSM?.connectedLu ?: ""
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -701,6 +732,7 @@ fun MainScreen(
|
|||||||
|
|
||||||
if (showFindDialog) {
|
if (showFindDialog) {
|
||||||
FindDialog(
|
FindDialog(
|
||||||
|
cols = cols,
|
||||||
onDismiss = { showFindDialog = false },
|
onDismiss = { showFindDialog = false },
|
||||||
onFind = { query: String, matchCase: Boolean, forward: Boolean ->
|
onFind = { query: String, matchCase: Boolean, forward: Boolean ->
|
||||||
viewModel.find(query, matchCase, forward)
|
viewModel.find(query, matchCase, forward)
|
||||||
|
|||||||
@@ -18,8 +18,7 @@ import haus.nightmare.lib3270j.TerminalModel
|
|||||||
import haus.nightmare.lib3270j.ft.FTConfig
|
import haus.nightmare.lib3270j.ft.FTConfig
|
||||||
import haus.nightmare.lib3270j.listener.ConnectionListener
|
import haus.nightmare.lib3270j.listener.ConnectionListener
|
||||||
import haus.nightmare.lib3270j.listener.ScreenUpdateListener
|
import haus.nightmare.lib3270j.listener.ScreenUpdateListener
|
||||||
import haus.nightmare.lib3270j.protocol.DS3270Constants.AID_ENTER
|
import haus.nightmare.lib3270j.protocol.DS3270Constants.*
|
||||||
import haus.nightmare.lib3270j.protocol.DS3270Constants.faIsProtected
|
|
||||||
import haus.nightmare.lib3270j.screen.ScreenBuffer
|
import haus.nightmare.lib3270j.screen.ScreenBuffer
|
||||||
import haus.nightmare.a3270.ft.FileTransfer
|
import haus.nightmare.a3270.ft.FileTransfer
|
||||||
import haus.nightmare.a3270.service.TerminalService
|
import haus.nightmare.a3270.service.TerminalService
|
||||||
@@ -44,6 +43,16 @@ sealed interface TerminalInputAction {
|
|||||||
data object EraseInput : TerminalInputAction
|
data object EraseInput : TerminalInputAction
|
||||||
data object Newline : TerminalInputAction
|
data object Newline : TerminalInputAction
|
||||||
data object Reset : TerminalInputAction
|
data object Reset : TerminalInputAction
|
||||||
|
data object WordLeft : TerminalInputAction
|
||||||
|
data object WordRight : TerminalInputAction
|
||||||
|
data object FieldEnd : TerminalInputAction
|
||||||
|
data object DeleteWord : TerminalInputAction
|
||||||
|
data object CursorSelect : TerminalInputAction
|
||||||
|
data object Attn : TerminalInputAction
|
||||||
|
data object SysReq : TerminalInputAction
|
||||||
|
data object Dup : TerminalInputAction
|
||||||
|
data object FieldMark : TerminalInputAction
|
||||||
|
data object ToggleInsert : 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
|
||||||
}
|
}
|
||||||
@@ -205,12 +214,22 @@ class TerminalViewModel(application: Application) : AndroidViewModel(application
|
|||||||
val c = client ?: return
|
val c = client ?: return
|
||||||
val ip = c.inputProcessor
|
val ip = c.inputProcessor
|
||||||
val buf = c.screenBuffer
|
val buf = c.screenBuffer
|
||||||
|
val isNvt = c.connectionState.isNvt || ip.isNvtMode
|
||||||
|
|
||||||
try {
|
try {
|
||||||
when (action) {
|
when (action) {
|
||||||
is TerminalInputAction.TypeText -> {
|
is TerminalInputAction.TypeText -> {
|
||||||
log.info("TypeText: text='${action.text}', curAddr=${buf.cursorAddress}, formatted=${buf.isFormatted}")
|
log.info("TypeText: text='${action.text}', curAddr=${buf.cursorAddress}, formatted=${buf.isFormatted}, isNvt=$isNvt")
|
||||||
ip.isKeyboardLocked = false
|
ip.isKeyboardLocked = false
|
||||||
|
if (isNvt) {
|
||||||
|
for (ch in action.text) {
|
||||||
|
if (ch == '\n' || ch == '\r') {
|
||||||
|
c.sendNVTString("\r\n")
|
||||||
|
} else {
|
||||||
|
c.sendNVTChar(ch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
for (ch in action.text) {
|
for (ch in action.text) {
|
||||||
if (ch == '\n' || ch == '\r') {
|
if (ch == '\n' || ch == '\r') {
|
||||||
ip.setKeyboardLocked(false)
|
ip.setKeyboardLocked(false)
|
||||||
@@ -221,53 +240,175 @@ class TerminalViewModel(application: Application) : AndroidViewModel(application
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
is TerminalInputAction.SendAid -> {
|
is TerminalInputAction.SendAid -> {
|
||||||
|
if (isNvt) {
|
||||||
|
if (action.aidCode == AID_ENTER) {
|
||||||
|
c.sendNVTString("\r\n")
|
||||||
|
} else if (action.aidCode == AID_CLEAR) {
|
||||||
|
c.sendNVTChar('\u000C')
|
||||||
|
} else {
|
||||||
|
val pfNum = aidToPfNumber(action.aidCode)
|
||||||
|
if (pfNum > 0) {
|
||||||
|
val seq = c.nvtProcessor?.getFunctionKeySequence(pfNum) ?: ""
|
||||||
|
if (seq.isNotEmpty()) {
|
||||||
|
c.sendNVTString(seq)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
ip.setKeyboardLocked(false)
|
ip.setKeyboardLocked(false)
|
||||||
ip.sendAid(action.aidCode)
|
ip.sendAid(action.aidCode)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
is TerminalInputAction.Backspace -> {
|
is TerminalInputAction.Backspace -> {
|
||||||
|
if (isNvt) {
|
||||||
|
c.sendNVTChar('\b')
|
||||||
|
} else {
|
||||||
ip.isKeyboardLocked = false
|
ip.isKeyboardLocked = false
|
||||||
ip.backspace()
|
ip.backspace()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
is TerminalInputAction.DeleteChar -> {
|
is TerminalInputAction.DeleteChar -> {
|
||||||
|
if (isNvt) {
|
||||||
|
c.sendNVTString("\u001B[3~")
|
||||||
|
} else {
|
||||||
ip.isKeyboardLocked = false
|
ip.isKeyboardLocked = false
|
||||||
ip.deleteChar()
|
ip.deleteChar()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
is TerminalInputAction.Tab -> {
|
is TerminalInputAction.Tab -> {
|
||||||
|
if (isNvt) {
|
||||||
|
c.sendNVTChar('\t')
|
||||||
|
} else {
|
||||||
ip.tab()
|
ip.tab()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
is TerminalInputAction.BackTab -> {
|
is TerminalInputAction.BackTab -> {
|
||||||
|
if (isNvt) {
|
||||||
|
c.sendNVTString("\u001B[Z")
|
||||||
|
} else {
|
||||||
ip.backTab()
|
ip.backTab()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
is TerminalInputAction.CursorLeft -> {
|
is TerminalInputAction.CursorLeft -> {
|
||||||
|
if (isNvt) {
|
||||||
|
c.sendNVTString("\u001B[D")
|
||||||
|
} else {
|
||||||
ip.cursorLeft()
|
ip.cursorLeft()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
is TerminalInputAction.CursorRight -> {
|
is TerminalInputAction.CursorRight -> {
|
||||||
|
if (isNvt) {
|
||||||
|
c.sendNVTString("\u001B[C")
|
||||||
|
} else {
|
||||||
ip.cursorRight()
|
ip.cursorRight()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
is TerminalInputAction.CursorUp -> {
|
is TerminalInputAction.CursorUp -> {
|
||||||
|
if (isNvt) {
|
||||||
|
c.sendNVTString("\u001B[A")
|
||||||
|
} else {
|
||||||
ip.cursorUp()
|
ip.cursorUp()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
is TerminalInputAction.CursorDown -> {
|
is TerminalInputAction.CursorDown -> {
|
||||||
|
if (isNvt) {
|
||||||
|
c.sendNVTString("\u001B[B")
|
||||||
|
} else {
|
||||||
ip.cursorDown()
|
ip.cursorDown()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
is TerminalInputAction.CursorHome -> {
|
is TerminalInputAction.CursorHome -> {
|
||||||
|
if (isNvt) {
|
||||||
|
c.sendNVTString("\u001B[H")
|
||||||
|
} else {
|
||||||
ip.cursorHome()
|
ip.cursorHome()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
is TerminalInputAction.EraseEof -> {
|
is TerminalInputAction.EraseEof -> {
|
||||||
|
if (isNvt) {
|
||||||
|
c.sendNVTString("\u001B[F")
|
||||||
|
} else {
|
||||||
ip.isKeyboardLocked = false
|
ip.isKeyboardLocked = false
|
||||||
ip.eraseEof()
|
ip.eraseEof()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
is TerminalInputAction.EraseInput -> {
|
is TerminalInputAction.EraseInput -> {
|
||||||
|
if (!isNvt) {
|
||||||
ip.isKeyboardLocked = false
|
ip.isKeyboardLocked = false
|
||||||
ip.eraseInput()
|
ip.eraseInput()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
is TerminalInputAction.Newline -> {
|
is TerminalInputAction.Newline -> {
|
||||||
|
if (isNvt) {
|
||||||
|
c.sendNVTString("\r\n")
|
||||||
|
} else {
|
||||||
ip.newline()
|
ip.newline()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
is TerminalInputAction.Reset -> {
|
is TerminalInputAction.Reset -> {
|
||||||
|
if (isNvt) {
|
||||||
|
c.sendNVTChar('\u001B')
|
||||||
|
} else {
|
||||||
ip.reset()
|
ip.reset()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
is TerminalInputAction.WordLeft -> {
|
||||||
|
if (isNvt) {
|
||||||
|
c.sendNVTString("\u001Bb")
|
||||||
|
} else {
|
||||||
|
ip.processWordLeft()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is TerminalInputAction.WordRight -> {
|
||||||
|
if (isNvt) {
|
||||||
|
c.sendNVTString("\u001Bf")
|
||||||
|
} else {
|
||||||
|
ip.processWordRight()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is TerminalInputAction.FieldEnd -> {
|
||||||
|
if (!isNvt) {
|
||||||
|
ip.processFieldEnd()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is TerminalInputAction.DeleteWord -> {
|
||||||
|
if (!isNvt) {
|
||||||
|
ip.isKeyboardLocked = false
|
||||||
|
ip.processDeleteWord()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is TerminalInputAction.CursorSelect -> {
|
||||||
|
if (!isNvt) {
|
||||||
|
ip.cursorSelect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is TerminalInputAction.Attn -> {
|
||||||
|
if (!isNvt) {
|
||||||
|
ip.attn()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is TerminalInputAction.SysReq -> {
|
||||||
|
if (!isNvt) {
|
||||||
|
ip.sysReq()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is TerminalInputAction.Dup -> {
|
||||||
|
if (!isNvt) {
|
||||||
|
ip.dup()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is TerminalInputAction.FieldMark -> {
|
||||||
|
if (!isNvt) {
|
||||||
|
ip.fieldMark()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is TerminalInputAction.ToggleInsert -> {
|
||||||
|
if (!isNvt) {
|
||||||
|
ip.processToggleInsert()
|
||||||
|
}
|
||||||
|
}
|
||||||
is TerminalInputAction.SetCursor -> {
|
is TerminalInputAction.SetCursor -> {
|
||||||
if (action.baddr in 0 until (buf.rows * buf.cols)) {
|
if (action.baddr in 0 until (buf.rows * buf.cols)) {
|
||||||
buf.cursorAddress = action.baddr
|
buf.cursorAddress = action.baddr
|
||||||
@@ -286,6 +427,18 @@ class TerminalViewModel(application: Application) : AndroidViewModel(application
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun aidToPfNumber(aid: Int): Int {
|
||||||
|
return when (aid) {
|
||||||
|
AID_PF1 -> 1; AID_PF2 -> 2; AID_PF3 -> 3; AID_PF4 -> 4
|
||||||
|
AID_PF5 -> 5; AID_PF6 -> 6; AID_PF7 -> 7; AID_PF8 -> 8
|
||||||
|
AID_PF9 -> 9; AID_PF10 -> 10; AID_PF11 -> 11; AID_PF12 -> 12
|
||||||
|
AID_PF13 -> 13; AID_PF14 -> 14; AID_PF15 -> 15; AID_PF16 -> 16
|
||||||
|
AID_PF17 -> 17; AID_PF18 -> 18; AID_PF19 -> 19; AID_PF20 -> 20
|
||||||
|
AID_PF21 -> 21; AID_PF22 -> 22; AID_PF23 -> 23; AID_PF24 -> 24
|
||||||
|
else -> 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun resolveUntrustedCert(accept: Boolean) {
|
fun resolveUntrustedCert(accept: Boolean) {
|
||||||
try {
|
try {
|
||||||
val prompt = _untrustedCertPrompt.value
|
val prompt = _untrustedCertPrompt.value
|
||||||
@@ -492,6 +645,32 @@ class TerminalViewModel(application: Application) : AndroidViewModel(application
|
|||||||
val newClient = Telnet3270Client(config)
|
val newClient = Telnet3270Client(config)
|
||||||
this@TerminalViewModel.client = newClient
|
this@TerminalViewModel.client = newClient
|
||||||
|
|
||||||
|
newClient.setNvtClipboardHandler(object : haus.nightmare.lib3270j.nvt.NvtProcessor.ClipboardHandler {
|
||||||
|
override fun getClipboardText(): String {
|
||||||
|
return try {
|
||||||
|
val cm = getApplication<Application>().getSystemService(android.content.Context.CLIPBOARD_SERVICE) as? android.content.ClipboardManager
|
||||||
|
cm?.primaryClip?.getItemAt(0)?.text?.toString() ?: ""
|
||||||
|
} catch (e: Exception) {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setClipboardText(text: String?) {
|
||||||
|
if (!text.isNullOrEmpty()) {
|
||||||
|
try {
|
||||||
|
val cm = getApplication<Application>().getSystemService(android.content.Context.CLIPBOARD_SERVICE) as? android.content.ClipboardManager
|
||||||
|
cm?.setPrimaryClip(android.content.ClipData.newPlainText("3270 NVT", text))
|
||||||
|
} catch (ignored: Exception) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
newClient.addNvtTitleListener { title ->
|
||||||
|
if (!title.isNullOrBlank()) {
|
||||||
|
_oiaText.value = title
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
newClient.addConnectionListener(object : ConnectionListener {
|
newClient.addConnectionListener(object : ConnectionListener {
|
||||||
override fun onConnectionStateChanged(oldState: ConnectionState, newState: ConnectionState) {
|
override fun onConnectionStateChanged(oldState: ConnectionState, newState: ConnectionState) {
|
||||||
_connectionState.value = newState
|
_connectionState.value = newState
|
||||||
@@ -721,6 +900,46 @@ class TerminalViewModel(application: Application) : AndroidViewModel(application
|
|||||||
inputChannel.trySend(TerminalInputAction.Reset)
|
inputChannel.trySend(TerminalInputAction.Reset)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun wordLeft() {
|
||||||
|
inputChannel.trySend(TerminalInputAction.WordLeft)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun wordRight() {
|
||||||
|
inputChannel.trySend(TerminalInputAction.WordRight)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun fieldEnd() {
|
||||||
|
inputChannel.trySend(TerminalInputAction.FieldEnd)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deleteWord() {
|
||||||
|
inputChannel.trySend(TerminalInputAction.DeleteWord)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cursorSelect() {
|
||||||
|
inputChannel.trySend(TerminalInputAction.CursorSelect)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun attn() {
|
||||||
|
inputChannel.trySend(TerminalInputAction.Attn)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sysReq() {
|
||||||
|
inputChannel.trySend(TerminalInputAction.SysReq)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun dup() {
|
||||||
|
inputChannel.trySend(TerminalInputAction.Dup)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun fieldMark() {
|
||||||
|
inputChannel.trySend(TerminalInputAction.FieldMark)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toggleInsert() {
|
||||||
|
inputChannel.trySend(TerminalInputAction.ToggleInsert)
|
||||||
|
}
|
||||||
|
|
||||||
fun eraseEof() {
|
fun eraseEof() {
|
||||||
inputChannel.trySend(TerminalInputAction.EraseEof)
|
inputChannel.trySend(TerminalInputAction.EraseEof)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -132,6 +132,51 @@ object HostStorage {
|
|||||||
saveAll(context, currentHosts)
|
saveAll(context, currentHosts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val AVAILABLE_CODE_PAGES = listOf(
|
||||||
|
"037" to "037 - US / Canada / Brazil",
|
||||||
|
"1047" to "1047 - IBM Open Systems / z/OS Unix",
|
||||||
|
"500" to "500 - International Latin-1",
|
||||||
|
"273" to "273 - Germany / Austria",
|
||||||
|
"277" to "277 - Denmark / Norway",
|
||||||
|
"278" to "278 - Sweden / Finland",
|
||||||
|
"280" to "280 - Italy",
|
||||||
|
"284" to "284 - Spain / Latin America",
|
||||||
|
"285" to "285 - United Kingdom",
|
||||||
|
"297" to "297 - France",
|
||||||
|
"420" to "420 - Arabic Bilingual",
|
||||||
|
"424" to "424 - Hebrew (with Latin)",
|
||||||
|
"803" to "803 - Hebrew Character Set",
|
||||||
|
"838" to "838 - Thai Extended",
|
||||||
|
"1160" to "1160 - Thai Euro",
|
||||||
|
"870" to "870 - Eastern Europe / Latin-2",
|
||||||
|
"871" to "871 - Iceland",
|
||||||
|
"875" to "875 - Greece (Greek)",
|
||||||
|
"880" to "880 - Cyrillic (Russian)",
|
||||||
|
"1025" to "1025 - Cyrillic Multilingual",
|
||||||
|
"1123" to "1123 - Cyrillic Ukraine",
|
||||||
|
"1154" to "1154 - Cyrillic Euro",
|
||||||
|
"905" to "905 - Turkey (Latin-5)",
|
||||||
|
"1026" to "1026 - Turkey (Turkish)",
|
||||||
|
"1155" to "1155 - Turkey Euro",
|
||||||
|
"1140" to "1140 - US / Canada (Euro €)",
|
||||||
|
"1141" to "1141 - Germany / Austria (Euro €)",
|
||||||
|
"1142" to "1142 - Denmark / Norway (Euro €)",
|
||||||
|
"1143" to "1143 - Sweden / Finland (Euro €)",
|
||||||
|
"1144" to "1144 - Italy (Euro €)",
|
||||||
|
"1145" to "1145 - Spain / Latin America (Euro €)",
|
||||||
|
"1146" to "1146 - United Kingdom (Euro €)",
|
||||||
|
"1147" to "1147 - France (Euro €)",
|
||||||
|
"1148" to "1148 - International (Euro €)",
|
||||||
|
"1149" to "1149 - Iceland (Euro €)",
|
||||||
|
"930" to "930 - Japanese Katakana Mixed DBCS",
|
||||||
|
"939" to "939 - Japanese Latin Mixed DBCS",
|
||||||
|
"935" to "935 - Simplified Chinese Mixed DBCS",
|
||||||
|
"937" to "937 - Traditional Chinese Mixed DBCS",
|
||||||
|
"1388" to "1388 - Simplified Chinese Extended DBCS",
|
||||||
|
"1371" to "1371 - Traditional Chinese Extended DBCS",
|
||||||
|
"933" to "933 - Korean Mixed DBCS"
|
||||||
|
)
|
||||||
|
|
||||||
fun getAutoConnectHost(context: Context): SavedHost? {
|
fun getAutoConnectHost(context: Context): SavedHost? {
|
||||||
return getSavedHosts(context).firstOrNull { it.autoConnect }
|
return getSavedHosts(context).firstOrNull { it.autoConnect }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -467,30 +467,7 @@ fun ConnectDialog(
|
|||||||
|
|
||||||
// Code Page Selector
|
// Code Page Selector
|
||||||
var codePageMenuExpanded by remember { mutableStateOf(false) }
|
var codePageMenuExpanded by remember { mutableStateOf(false) }
|
||||||
val codePagesList = listOf(
|
val codePagesList = HostStorage.AVAILABLE_CODE_PAGES
|
||||||
"037" to "037 - US / Canada / Brazil",
|
|
||||||
"1047" to "1047 - IBM Open Systems / z/OS Unix",
|
|
||||||
"500" to "500 - International Latin-1",
|
|
||||||
"273" to "273 - Germany / Austria",
|
|
||||||
"277" to "277 - Denmark / Norway",
|
|
||||||
"278" to "278 - Sweden / Finland",
|
|
||||||
"280" to "280 - Italy",
|
|
||||||
"284" to "284 - Spain / Latin America",
|
|
||||||
"285" to "285 - United Kingdom",
|
|
||||||
"297" to "297 - France",
|
|
||||||
"870" to "870 - Eastern Europe / Latin-2",
|
|
||||||
"871" to "871 - Iceland",
|
|
||||||
"875" to "875 - Greece (Greek)",
|
|
||||||
"1026" to "1026 - Turkey (Turkish)",
|
|
||||||
"1140" to "1140 - US / Canada (Euro €)",
|
|
||||||
"1141" to "1141 - Germany / Austria (Euro €)",
|
|
||||||
"1148" to "1148 - International (Euro €)",
|
|
||||||
"930" to "930 - Japanese Katakana Mixed DBCS",
|
|
||||||
"939" to "939 - Japanese Latin Mixed DBCS",
|
|
||||||
"935" to "935 - Simplified Chinese Mixed DBCS",
|
|
||||||
"937" to "937 - Traditional Chinese Mixed DBCS",
|
|
||||||
"933" to "933 - Korean Mixed DBCS"
|
|
||||||
)
|
|
||||||
val selectedCpLabel = codePagesList.firstOrNull { it.first == codePage }?.second ?: "Code Page $codePage"
|
val selectedCpLabel = codePagesList.firstOrNull { it.first == codePage }?.second ?: "Code Page $codePage"
|
||||||
|
|
||||||
Column(modifier = Modifier.fillMaxWidth()) {
|
Column(modifier = Modifier.fillMaxWidth()) {
|
||||||
|
|||||||
@@ -134,6 +134,7 @@ fun FieldInspectorDialog(
|
|||||||
Text("Hi-Int", color = Color.LightGray, fontSize = 11.sp, fontWeight = FontWeight.Bold, modifier = Modifier.width(44.dp), textAlign = TextAlign.Center)
|
Text("Hi-Int", color = Color.LightGray, fontSize = 11.sp, fontWeight = FontWeight.Bold, modifier = Modifier.width(44.dp), textAlign = TextAlign.Center)
|
||||||
Text("Hidden", color = Color.LightGray, fontSize = 11.sp, fontWeight = FontWeight.Bold, modifier = Modifier.width(46.dp), textAlign = TextAlign.Center)
|
Text("Hidden", color = Color.LightGray, fontSize = 11.sp, fontWeight = FontWeight.Bold, modifier = Modifier.width(46.dp), textAlign = TextAlign.Center)
|
||||||
Text("Pen", color = Color.LightGray, fontSize = 11.sp, fontWeight = FontWeight.Bold, modifier = Modifier.width(36.dp), textAlign = TextAlign.Center)
|
Text("Pen", color = Color.LightGray, fontSize = 11.sp, fontWeight = FontWeight.Bold, modifier = Modifier.width(36.dp), textAlign = TextAlign.Center)
|
||||||
|
Text("Wrap", color = Color.LightGray, fontSize = 11.sp, fontWeight = FontWeight.Bold, modifier = Modifier.width(36.dp), textAlign = TextAlign.Center)
|
||||||
Text("Content Text", color = Color.LightGray, fontSize = 11.sp, fontWeight = FontWeight.Bold, modifier = Modifier.width(220.dp), textAlign = TextAlign.Start)
|
Text("Content Text", color = Color.LightGray, fontSize = 11.sp, fontWeight = FontWeight.Bold, modifier = Modifier.width(220.dp), textAlign = TextAlign.Start)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,6 +177,7 @@ fun FieldInspectorDialog(
|
|||||||
Text(if (field.isHighIntensity) "Y" else "-", color = if (field.isHighIntensity) Color.White else Color.Gray, fontSize = 10.sp, fontWeight = FontWeight.Bold, modifier = Modifier.width(44.dp), textAlign = TextAlign.Center)
|
Text(if (field.isHighIntensity) "Y" else "-", color = if (field.isHighIntensity) Color.White else Color.Gray, fontSize = 10.sp, fontWeight = FontWeight.Bold, modifier = Modifier.width(44.dp), textAlign = TextAlign.Center)
|
||||||
Text(if (field.isHidden) "Y" else "-", color = if (field.isHidden) Color(0xFFFF6B6B) else Color.Gray, fontSize = 10.sp, modifier = Modifier.width(46.dp), textAlign = TextAlign.Center)
|
Text(if (field.isHidden) "Y" else "-", color = if (field.isHidden) Color(0xFFFF6B6B) else Color.Gray, fontSize = 10.sp, modifier = Modifier.width(46.dp), textAlign = TextAlign.Center)
|
||||||
Text(if (field.isPenSelectable) "Y" else "-", color = if (field.isPenSelectable) Color.Yellow else Color.Gray, fontSize = 10.sp, modifier = Modifier.width(36.dp), textAlign = TextAlign.Center)
|
Text(if (field.isPenSelectable) "Y" else "-", color = if (field.isPenSelectable) Color.Yellow else Color.Gray, fontSize = 10.sp, modifier = Modifier.width(36.dp), textAlign = TextAlign.Center)
|
||||||
|
Text(if (field.isWrapped) "↩" else "-", color = if (field.isWrapped) Color(0xFF339AF0) else Color.Gray, fontSize = 10.sp, fontWeight = FontWeight.Bold, modifier = Modifier.width(36.dp), textAlign = TextAlign.Center)
|
||||||
Text(displayText, color = if (field.isProtected) Color(0xFF90CAF9) else Color(0xFFA5D6A7), fontSize = 11.sp, fontFamily = FontFamily.Monospace, maxLines = 1, modifier = Modifier.width(220.dp))
|
Text(displayText, color = if (field.isProtected) Color(0xFF90CAF9) else Color(0xFFA5D6A7), fontSize = 11.sp, fontFamily = FontFamily.Monospace, maxLines = 1, modifier = Modifier.width(220.dp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
package haus.nightmare.a3270.ui
|
package haus.nightmare.a3270.ui
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Environment
|
||||||
|
import android.provider.OpenableColumns
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
@@ -9,6 +17,7 @@ import androidx.compose.runtime.*
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.input.ImeAction
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||||
@@ -17,6 +26,10 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import haus.nightmare.lib3270j.ft.FTConfig
|
import haus.nightmare.lib3270j.ft.FTConfig
|
||||||
import haus.nightmare.a3270.FTProgressState
|
import haus.nightmare.a3270.FTProgressState
|
||||||
|
import java.io.File
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Date
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
private val TerminalKeyboardOptions = KeyboardOptions(
|
private val TerminalKeyboardOptions = KeyboardOptions(
|
||||||
capitalization = KeyboardCapitalization.None,
|
capitalization = KeyboardCapitalization.None,
|
||||||
@@ -33,6 +46,8 @@ fun FileTransferDialog(
|
|||||||
onStartTransfer: (FTConfig) -> Unit,
|
onStartTransfer: (FTConfig) -> Unit,
|
||||||
onCancelTransfer: () -> Unit = {}
|
onCancelTransfer: () -> Unit = {}
|
||||||
) {
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
val initialTypeEnum = when (initialHostType.uppercase()) {
|
val initialTypeEnum = when (initialHostType.uppercase()) {
|
||||||
"CMS", "VM/CMS", "VM" -> FTConfig.HostType.CMS
|
"CMS", "VM/CMS", "VM" -> FTConfig.HostType.CMS
|
||||||
"CICS" -> FTConfig.HostType.CICS
|
"CICS" -> FTConfig.HostType.CICS
|
||||||
@@ -41,6 +56,7 @@ fun FileTransferDialog(
|
|||||||
|
|
||||||
var hostFile by remember { mutableStateOf("") }
|
var hostFile by remember { mutableStateOf("") }
|
||||||
var localFile by remember { mutableStateOf("") }
|
var localFile by remember { mutableStateOf("") }
|
||||||
|
var localDisplayName by remember { mutableStateOf("") }
|
||||||
var isReceive by remember { mutableStateOf(true) }
|
var isReceive by remember { mutableStateOf(true) }
|
||||||
var isAscii by remember { mutableStateOf(true) }
|
var isAscii by remember { mutableStateOf(true) }
|
||||||
var hostType by remember { mutableStateOf(initialTypeEnum) }
|
var hostType by remember { mutableStateOf(initialTypeEnum) }
|
||||||
@@ -50,6 +66,74 @@ fun FileTransferDialog(
|
|||||||
var overwrite by remember { mutableStateOf(true) }
|
var overwrite by remember { mutableStateOf(true) }
|
||||||
var showBrowseDialog by remember { mutableStateOf(false) }
|
var showBrowseDialog by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
var targetSaveUri by remember { mutableStateOf<Uri?>(null) }
|
||||||
|
var lastSavedFile by remember { mutableStateOf<File?>(null) }
|
||||||
|
|
||||||
|
// SAF Launcher for uploading (Send / PUT)
|
||||||
|
val openDocumentLauncher = rememberLauncherForActivityResult(
|
||||||
|
contract = ActivityResultContracts.OpenDocument()
|
||||||
|
) { uri: Uri? ->
|
||||||
|
if (uri != null) {
|
||||||
|
try {
|
||||||
|
val displayName = getFileNameFromUri(context, uri).ifBlank { "upload.bin" }
|
||||||
|
val tempDir = File(context.cacheDir, "ft_upload").apply { if (!exists()) mkdirs() }
|
||||||
|
val tempFile = File(tempDir, displayName)
|
||||||
|
context.contentResolver.openInputStream(uri)?.use { input ->
|
||||||
|
tempFile.outputStream().use { output ->
|
||||||
|
input.copyTo(output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
localFile = tempFile.absolutePath
|
||||||
|
localDisplayName = displayName
|
||||||
|
if (hostFile.isBlank()) {
|
||||||
|
val cleanBase = displayName.substringBeforeLast('.').replace(Regex("[^a-zA-Z0-9]"), "").uppercase()
|
||||||
|
hostFile = if (hostType == FTConfig.HostType.TSO) "'$cleanBase'" else "$cleanBase FILE A"
|
||||||
|
}
|
||||||
|
Toast.makeText(context, "Selected: $displayName", Toast.LENGTH_SHORT).show()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Toast.makeText(context, "Failed to read file: ${e.message}", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SAF Launcher for choosing download destination (Receive / GET)
|
||||||
|
val createDocumentLauncher = rememberLauncherForActivityResult(
|
||||||
|
contract = ActivityResultContracts.CreateDocument("*/*")
|
||||||
|
) { uri: Uri? ->
|
||||||
|
if (uri != null) {
|
||||||
|
targetSaveUri = uri
|
||||||
|
val displayName = getFileNameFromUri(context, uri).ifBlank { "download.bin" }
|
||||||
|
localDisplayName = displayName
|
||||||
|
val tempDir = File(context.cacheDir, "ft_download").apply { if (!exists()) mkdirs() }
|
||||||
|
val tempFile = File(tempDir, displayName)
|
||||||
|
localFile = tempFile.absolutePath
|
||||||
|
Toast.makeText(context, "Destination: $displayName", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Watch for completed transfers to export to destination URI if chosen
|
||||||
|
LaunchedEffect(ftProgressState.isActive, ftProgressState.isError) {
|
||||||
|
if (!ftProgressState.isActive && !ftProgressState.isError && ftProgressState.bytesTransferred > 0 && isReceive && localFile.isNotBlank()) {
|
||||||
|
val src = File(localFile)
|
||||||
|
if (src.exists()) {
|
||||||
|
lastSavedFile = src
|
||||||
|
val uri = targetSaveUri
|
||||||
|
if (uri != null) {
|
||||||
|
try {
|
||||||
|
context.contentResolver.openOutputStream(uri)?.use { out ->
|
||||||
|
src.inputStream().use { input ->
|
||||||
|
input.copyTo(out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Toast.makeText(context, "Saved download to destination file", Toast.LENGTH_SHORT).show()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Toast.makeText(context, "Failed writing to destination: ${e.message}", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (showBrowseDialog) {
|
if (showBrowseDialog) {
|
||||||
HostDirectoryDialog(
|
HostDirectoryDialog(
|
||||||
client = client,
|
client = client,
|
||||||
@@ -58,11 +142,61 @@ fun FileTransferDialog(
|
|||||||
onDismiss = { showBrowseDialog = false },
|
onDismiss = { showBrowseDialog = false },
|
||||||
onSelectDataset = { selected ->
|
onSelectDataset = { selected ->
|
||||||
hostFile = selected
|
hostFile = selected
|
||||||
|
if (localFile.isBlank()) {
|
||||||
|
val base = selected.trim('\'').substringAfterLast('.').substringAfterLast(' ')
|
||||||
|
val safeName = if (base.isNotBlank()) "$base.txt" else "download.txt"
|
||||||
|
val tempDir = File(context.cacheDir, "ft_download").apply { if (!exists()) mkdirs() }
|
||||||
|
val tempFile = File(tempDir, safeName)
|
||||||
|
localFile = tempFile.absolutePath
|
||||||
|
localDisplayName = safeName
|
||||||
|
}
|
||||||
showBrowseDialog = false
|
showBrowseDialog = false
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun exportToDownloads() {
|
||||||
|
val src = lastSavedFile ?: if (localFile.isNotBlank()) File(localFile) else null
|
||||||
|
if (src == null || !src.exists()) {
|
||||||
|
Toast.makeText(context, "Downloaded file not found", Toast.LENGTH_SHORT).show()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
val downloadsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
|
||||||
|
if (!downloadsDir.exists()) downloadsDir.mkdirs()
|
||||||
|
val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(Date())
|
||||||
|
val targetName = (if (localDisplayName.isNotBlank()) localDisplayName else src.name).let {
|
||||||
|
val base = it.substringBeforeLast('.')
|
||||||
|
val ext = if (it.contains('.')) "." + it.substringAfterLast('.') else ""
|
||||||
|
"${base}_$timeStamp$ext"
|
||||||
|
}
|
||||||
|
val dst = File(downloadsDir, targetName)
|
||||||
|
src.copyTo(dst, overwrite = true)
|
||||||
|
Toast.makeText(context, "Exported to Downloads/${dst.name}", Toast.LENGTH_LONG).show()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Toast.makeText(context, "Export failed: ${e.message}", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun shareDownloadedFile() {
|
||||||
|
val src = lastSavedFile ?: if (localFile.isNotBlank()) File(localFile) else null
|
||||||
|
if (src == null || !src.exists()) {
|
||||||
|
Toast.makeText(context, "Downloaded file not found", Toast.LENGTH_SHORT).show()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
val text = src.readText()
|
||||||
|
val intent = Intent(Intent.ACTION_SEND).apply {
|
||||||
|
type = "text/plain"
|
||||||
|
putExtra(Intent.EXTRA_SUBJECT, localDisplayName.ifBlank { src.name })
|
||||||
|
putExtra(Intent.EXTRA_TEXT, text)
|
||||||
|
}
|
||||||
|
context.startActivity(Intent.createChooser(intent, "Share Transferred File"))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Toast.makeText(context, "Share failed: ${e.message}", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = onDismiss,
|
onDismissRequest = onDismiss,
|
||||||
title = {
|
title = {
|
||||||
@@ -113,13 +247,70 @@ fun FileTransferDialog(
|
|||||||
}
|
}
|
||||||
HorizontalDivider(color = Color(0xFF373A40))
|
HorizontalDivider(color = Color(0xFF373A40))
|
||||||
} else if (ftProgressState.statusMessage.isNotBlank()) {
|
} else if (ftProgressState.statusMessage.isNotBlank()) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||||
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = ftProgressState.statusMessage,
|
text = ftProgressState.statusMessage,
|
||||||
fontSize = 12.sp,
|
fontSize = 12.sp,
|
||||||
|
fontWeight = FontWeight.SemiBold,
|
||||||
color = if (ftProgressState.isError) Color(0xFFFF6B6B) else Color(0xFF51CF66)
|
color = if (ftProgressState.isError) Color(0xFFFF6B6B) else Color(0xFF51CF66)
|
||||||
)
|
)
|
||||||
|
if (!ftProgressState.isError && isReceive && lastSavedFile?.exists() == true) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(6.dp)
|
||||||
|
) {
|
||||||
|
OutlinedButton(
|
||||||
|
onClick = { exportToDownloads() },
|
||||||
|
contentPadding = PaddingValues(horizontal = 8.dp, vertical = 2.dp),
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
) {
|
||||||
|
Text("Save to Downloads 📥", fontSize = 11.sp)
|
||||||
|
}
|
||||||
|
OutlinedButton(
|
||||||
|
onClick = { shareDownloadedFile() },
|
||||||
|
contentPadding = PaddingValues(horizontal = 8.dp, vertical = 2.dp),
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
) {
|
||||||
|
Text("Share 📤", fontSize = 11.sp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HorizontalDivider(color = Color(0xFF373A40))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Transfer Direction
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Text("Direction:", fontSize = 12.sp, fontWeight = FontWeight.SemiBold)
|
||||||
|
Row {
|
||||||
|
FilterChip(
|
||||||
|
selected = isReceive,
|
||||||
|
onClick = {
|
||||||
|
isReceive = true
|
||||||
|
targetSaveUri = null
|
||||||
|
},
|
||||||
|
label = { Text("Receive (GET)", fontSize = 11.sp) }
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(4.dp))
|
||||||
|
FilterChip(
|
||||||
|
selected = !isReceive,
|
||||||
|
onClick = {
|
||||||
|
isReceive = false
|
||||||
|
targetSaveUri = null
|
||||||
|
},
|
||||||
|
label = { Text("Send (PUT)", fontSize = 11.sp) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Host File Name
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
@@ -145,39 +336,52 @@ fun FileTransferDialog(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Local File Selection (SAF Document Picker integration)
|
||||||
|
Column(modifier = Modifier.fillMaxWidth()) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(6.dp)
|
||||||
|
) {
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = localFile,
|
value = if (localDisplayName.isNotBlank()) localDisplayName else localFile,
|
||||||
onValueChange = { localFile = it },
|
onValueChange = {
|
||||||
label = { Text("Local File Name / Path") },
|
localDisplayName = it
|
||||||
placeholder = { Text("sample.txt") },
|
localFile = it
|
||||||
supportingText = { Text("Relative names will be stored in app Downloads/Files storage", fontSize = 10.sp) },
|
},
|
||||||
|
label = { Text(if (isReceive) "Local Destination" else "Local File to Upload") },
|
||||||
|
placeholder = { Text(if (isReceive) "download.txt" else "sample.txt") },
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
keyboardOptions = TerminalKeyboardOptions.copy(
|
keyboardOptions = TerminalKeyboardOptions.copy(
|
||||||
imeAction = if (!isReceive && hostType == FTConfig.HostType.TSO) ImeAction.Next else ImeAction.Done
|
imeAction = if (!isReceive && hostType == FTConfig.HostType.TSO) ImeAction.Next else ImeAction.Done
|
||||||
),
|
),
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.weight(1f)
|
||||||
)
|
|
||||||
|
|
||||||
// Transfer Direction
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
Text("Direction:", fontSize = 12.sp, fontWeight = FontWeight.SemiBold)
|
|
||||||
Row {
|
|
||||||
FilterChip(
|
|
||||||
selected = isReceive,
|
|
||||||
onClick = { isReceive = true },
|
|
||||||
label = { Text("Receive (GET)", fontSize = 11.sp) }
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.width(4.dp))
|
|
||||||
FilterChip(
|
|
||||||
selected = !isReceive,
|
|
||||||
onClick = { isReceive = false },
|
|
||||||
label = { Text("Send (PUT)", fontSize = 11.sp) }
|
|
||||||
)
|
)
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
if (isReceive) {
|
||||||
|
val defName = if (hostFile.isNotBlank()) {
|
||||||
|
val base = hostFile.trim('\'').substringAfterLast('.').substringAfterLast(' ')
|
||||||
|
if (base.isNotBlank()) "$base.txt" else "download.txt"
|
||||||
|
} else "download.txt"
|
||||||
|
createDocumentLauncher.launch(defName)
|
||||||
|
} else {
|
||||||
|
openDocumentLauncher.launch(arrayOf("*/*"))
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF1C7ED6)),
|
||||||
|
contentPadding = PaddingValues(horizontal = 10.dp, vertical = 4.dp),
|
||||||
|
modifier = Modifier.padding(top = 6.dp)
|
||||||
|
) {
|
||||||
|
Text(if (isReceive) "Save As..." else "Pick File...", fontSize = 11.sp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Text(
|
||||||
|
text = if (isReceive) "Pick a destination or enter a file name to save in app storage" else "Use 'Pick File' to select any file from Downloads / Storage",
|
||||||
|
fontSize = 10.sp,
|
||||||
|
color = Color.LightGray,
|
||||||
|
modifier = Modifier.padding(start = 4.dp, top = 2.dp)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transfer Mode
|
// Transfer Mode
|
||||||
@@ -273,10 +477,18 @@ fun FileTransferDialog(
|
|||||||
confirmButton = {
|
confirmButton = {
|
||||||
Button(
|
Button(
|
||||||
onClick = {
|
onClick = {
|
||||||
if (hostFile.isNotBlank() && localFile.isNotBlank()) {
|
val resolvedLocal = if (localFile.isNotBlank()) {
|
||||||
|
localFile.trim()
|
||||||
|
} else if (localDisplayName.isNotBlank()) {
|
||||||
|
val tempDir = File(context.cacheDir, if (isReceive) "ft_download" else "ft_upload").apply { if (!exists()) mkdirs() }
|
||||||
|
File(tempDir, localDisplayName.trim()).absolutePath
|
||||||
|
} else ""
|
||||||
|
|
||||||
|
if (hostFile.isNotBlank() && resolvedLocal.isNotBlank()) {
|
||||||
|
localFile = resolvedLocal
|
||||||
val config = FTConfig().apply {
|
val config = FTConfig().apply {
|
||||||
setHostFilename(hostFile.trim())
|
setHostFilename(hostFile.trim())
|
||||||
setLocalFilename(localFile.trim())
|
setLocalFilename(resolvedLocal)
|
||||||
setDirection(if (isReceive) FTConfig.Direction.RECEIVE else FTConfig.Direction.SEND)
|
setDirection(if (isReceive) FTConfig.Direction.RECEIVE else FTConfig.Direction.SEND)
|
||||||
setTransferMode(if (isAscii) FTConfig.TransferMode.ASCII else FTConfig.TransferMode.BINARY)
|
setTransferMode(if (isAscii) FTConfig.TransferMode.ASCII else FTConfig.TransferMode.BINARY)
|
||||||
setHostType(hostType)
|
setHostType(hostType)
|
||||||
@@ -290,7 +502,7 @@ fun FileTransferDialog(
|
|||||||
onStartTransfer(config)
|
onStartTransfer(config)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
enabled = hostFile.isNotBlank() && localFile.isNotBlank() && !ftProgressState.isRunning
|
enabled = hostFile.isNotBlank() && (localFile.isNotBlank() || localDisplayName.isNotBlank()) && !ftProgressState.isRunning
|
||||||
) {
|
) {
|
||||||
Text("Start Transfer")
|
Text("Start Transfer")
|
||||||
}
|
}
|
||||||
@@ -302,3 +514,22 @@ fun FileTransferDialog(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getFileNameFromUri(context: Context, uri: Uri): String {
|
||||||
|
var name = ""
|
||||||
|
try {
|
||||||
|
val cursor = context.contentResolver.query(uri, null, null, null, null)
|
||||||
|
cursor?.use {
|
||||||
|
if (it.moveToFirst()) {
|
||||||
|
val nameIndex = it.getColumnIndex(OpenableColumns.DISPLAY_NAME)
|
||||||
|
if (nameIndex != -1) {
|
||||||
|
name = it.getString(nameIndex) ?: ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (_: Exception) {}
|
||||||
|
if (name.isBlank()) {
|
||||||
|
name = uri.lastPathSegment ?: "file"
|
||||||
|
}
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import androidx.compose.ui.window.DialogProperties
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun FindDialog(
|
fun FindDialog(
|
||||||
|
cols: Int = 80,
|
||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
onFind: (query: String, matchCase: Boolean, forward: Boolean) -> Int,
|
onFind: (query: String, matchCase: Boolean, forward: Boolean) -> Int,
|
||||||
onClearHighlight: () -> Unit
|
onClearHighlight: () -> Unit
|
||||||
@@ -49,8 +50,9 @@ fun FindDialog(
|
|||||||
}
|
}
|
||||||
val pos = onFind(trimmed, matchCase, searchForward)
|
val pos = onFind(trimmed, matchCase, searchForward)
|
||||||
if (pos >= 0) {
|
if (pos >= 0) {
|
||||||
val row = (pos / 80) + 1
|
val effectiveCols = if (cols > 0) cols else 80
|
||||||
val col = (pos % 80) + 1
|
val row = (pos / effectiveCols) + 1
|
||||||
|
val col = (pos % effectiveCols) + 1
|
||||||
statusMessage = "Found at position $pos ($row/$col)"
|
statusMessage = "Found at position $pos ($row/$col)"
|
||||||
isError = false
|
isError = false
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -29,6 +29,9 @@ fun OiaStatusBar(
|
|||||||
graphicsMode: String = "NONE",
|
graphicsMode: String = "NONE",
|
||||||
codePage: String = "037",
|
codePage: String = "037",
|
||||||
isLightPenMode: Boolean = false,
|
isLightPenMode: Boolean = false,
|
||||||
|
isInsertMode: Boolean = false,
|
||||||
|
inhibitReason: Int = 0,
|
||||||
|
luName: 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
|
||||||
@@ -37,20 +40,36 @@ fun OiaStatusBar(
|
|||||||
|
|
||||||
val hostText = if (currentHost.isNotBlank()) currentHost else oiaText
|
val hostText = if (currentHost.isNotBlank()) currentHost else oiaText
|
||||||
|
|
||||||
val lockStatusText = if (!connectionState.isConnected()) {
|
val connTypeBadge = when (connectionState) {
|
||||||
"OFFLINE"
|
ConnectionState.CONNECTED_3270 -> "TN3270"
|
||||||
} else if (isKeyboardLocked) {
|
ConnectionState.CONNECTED_TN3270E -> "TN3270E"
|
||||||
"X SYSTEM"
|
ConnectionState.CONNECTED_SSCP -> "SSCP-LU"
|
||||||
} else {
|
ConnectionState.CONNECTED_NVT, ConnectionState.CONNECTED_NVT_CHAR, ConnectionState.CONNECTED_E_NVT -> "NVT"
|
||||||
"READY"
|
ConnectionState.CONNECTED_UNBOUND -> "UNBOUND"
|
||||||
|
ConnectionState.TCP_PENDING, ConnectionState.TELNET_PENDING -> "CONNECTING"
|
||||||
|
ConnectionState.NOT_CONNECTED -> "OFFLINE"
|
||||||
|
else -> connectionState.name
|
||||||
}
|
}
|
||||||
|
|
||||||
val lockStatusColor = if (!connectionState.isConnected()) {
|
val (lockStatusText, lockStatusColor) = if (!connectionState.isConnected()) {
|
||||||
Color(0xFF868E96)
|
"OFFLINE" to Color(0xFF868E96)
|
||||||
|
} else if (inhibitReason != 0) {
|
||||||
|
val txt = when (inhibitReason) {
|
||||||
|
haus.nightmare.lib3270j.ecl.ECLConstants.INHIBIT_SYSTEM_LOCK -> "X SYSTEM"
|
||||||
|
haus.nightmare.lib3270j.ecl.ECLConstants.INHIBIT_COMM_CHECK -> "X COMM"
|
||||||
|
haus.nightmare.lib3270j.ecl.ECLConstants.INHIBIT_NUMERIC_ONLY -> "X NUM"
|
||||||
|
haus.nightmare.lib3270j.ecl.ECLConstants.INHIBIT_PROTECTED_FIELD -> "X PROT"
|
||||||
|
haus.nightmare.lib3270j.ecl.ECLConstants.INHIBIT_OVERFLOW -> "X >"
|
||||||
|
haus.nightmare.lib3270j.ecl.ECLConstants.INHIBIT_OPERATOR_DUE -> "X OP"
|
||||||
|
else -> "X LOCKED"
|
||||||
|
}
|
||||||
|
txt to Color(0xFFFF6B6B)
|
||||||
} else if (isKeyboardLocked) {
|
} else if (isKeyboardLocked) {
|
||||||
Color(0xFFFF6B6B) // Red for locked
|
"X SYSTEM" to Color(0xFFFF6B6B)
|
||||||
|
} else if (isInsertMode) {
|
||||||
|
"INSERT" to Color(0xFF339AF0)
|
||||||
} else {
|
} else {
|
||||||
Color(0xFF51CF66) // Green for ready
|
"READY" to Color(0xFF51CF66)
|
||||||
}
|
}
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
@@ -157,6 +176,42 @@ fun OiaStatusBar(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Connection Type Badge (TN3270, TN3270E, SSCP, NVT)
|
||||||
|
if (connectionState.isConnected()) {
|
||||||
|
Spacer(modifier = Modifier.width(6.dp))
|
||||||
|
Surface(
|
||||||
|
color = Color(0xFF7950F2).copy(alpha = 0.25f),
|
||||||
|
shape = RoundedCornerShape(4.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = connTypeBadge,
|
||||||
|
color = Color(0xFFB197FC),
|
||||||
|
fontSize = 9.sp,
|
||||||
|
fontFamily = FontFamily.Monospace,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
modifier = Modifier.padding(horizontal = 4.dp, vertical = 1.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LU Name Badge
|
||||||
|
if (connectionState.isConnected() && luName.isNotBlank()) {
|
||||||
|
Spacer(modifier = Modifier.width(6.dp))
|
||||||
|
Surface(
|
||||||
|
color = Color(0xFF40C057).copy(alpha = 0.2f),
|
||||||
|
shape = RoundedCornerShape(4.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "LU:$luName",
|
||||||
|
color = Color(0xFF69DB7C),
|
||||||
|
fontSize = 9.sp,
|
||||||
|
fontFamily = FontFamily.Monospace,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
modifier = Modifier.padding(horizontal = 4.dp, vertical = 1.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Terminal Keyboard Lock Status Badge (X SYSTEM vs READY)
|
// Terminal Keyboard Lock Status Badge (X SYSTEM vs READY)
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ fun PrinterSessionDialog(
|
|||||||
var assocDisplayLu by remember { mutableStateOf("") }
|
var assocDisplayLu by remember { mutableStateOf("") }
|
||||||
var codePage by remember { mutableStateOf(initialCodePage) }
|
var codePage by remember { mutableStateOf(initialCodePage) }
|
||||||
var useTls by remember { mutableStateOf(initialUseTls) }
|
var useTls by remember { mutableStateOf(initialUseTls) }
|
||||||
|
var pdtType by remember { mutableStateOf("DEFAULT") }
|
||||||
|
|
||||||
var isConnected by remember { mutableStateOf(false) }
|
var isConnected by remember { mutableStateOf(false) }
|
||||||
var sessionStatus by remember { mutableStateOf("Disconnected") }
|
var sessionStatus by remember { mutableStateOf("Disconnected") }
|
||||||
@@ -65,24 +66,7 @@ fun PrinterSessionDialog(
|
|||||||
|
|
||||||
var printerClient by remember { mutableStateOf<Telnet3270EPClient?>(null) }
|
var printerClient by remember { mutableStateOf<Telnet3270EPClient?>(null) }
|
||||||
|
|
||||||
val codePagesList = listOf(
|
val codePagesList = haus.nightmare.a3270.storage.HostStorage.AVAILABLE_CODE_PAGES
|
||||||
"037" to "037 - US / Canada",
|
|
||||||
"1047" to "1047 - Open Systems / Unix",
|
|
||||||
"500" to "500 - International",
|
|
||||||
"273" to "273 - Germany",
|
|
||||||
"277" to "277 - Denmark / Norway",
|
|
||||||
"278" to "278 - Sweden / Finland",
|
|
||||||
"280" to "280 - Italy",
|
|
||||||
"284" to "284 - Spain",
|
|
||||||
"285" to "285 - United Kingdom",
|
|
||||||
"297" to "297 - France",
|
|
||||||
"870" to "870 - Eastern Europe",
|
|
||||||
"1140" to "1140 - US (Euro €)",
|
|
||||||
"1141" to "1141 - Germany (Euro €)",
|
|
||||||
"1148" to "1148 - Intl (Euro €)",
|
|
||||||
"930" to "930 - Japanese Katakana DBCS",
|
|
||||||
"939" to "939 - Japanese Latin DBCS"
|
|
||||||
)
|
|
||||||
|
|
||||||
fun startSession() {
|
fun startSession() {
|
||||||
val port = portStr.trim().toIntOrNull() ?: 23
|
val port = portStr.trim().toIntOrNull() ?: 23
|
||||||
@@ -92,6 +76,16 @@ fun PrinterSessionDialog(
|
|||||||
if (printerLu.isNotBlank()) this.printerLuName = printerLu.trim()
|
if (printerLu.isNotBlank()) this.printerLuName = printerLu.trim()
|
||||||
if (assocDisplayLu.isNotBlank()) this.associatedDisplayLuName = assocDisplayLu.trim()
|
if (assocDisplayLu.isNotBlank()) this.associatedDisplayLuName = assocDisplayLu.trim()
|
||||||
this.destinationType = PrinterConfig.DestinationType.MEMORY
|
this.destinationType = PrinterConfig.DestinationType.MEMORY
|
||||||
|
|
||||||
|
val pdt = when (pdtType) {
|
||||||
|
"PCL5" -> haus.nightmare.lib3270j.printer.PrinterDefinitionTable.createPcl5PDT()
|
||||||
|
"EPSON_ESC_P" -> haus.nightmare.lib3270j.printer.PrinterDefinitionTable.createEpsonEscPPDT()
|
||||||
|
"POSTSCRIPT" -> haus.nightmare.lib3270j.printer.PrinterDefinitionTable.createPostScriptPDT()
|
||||||
|
else -> haus.nightmare.lib3270j.printer.PrinterDefinitionTable.createPlainTextPDT()
|
||||||
|
}
|
||||||
|
if (pdt != null) {
|
||||||
|
this.printerDefinitionTable = pdt
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val client = Telnet3270EPClient(config)
|
val client = Telnet3270EPClient(config)
|
||||||
@@ -313,16 +307,51 @@ fun PrinterSessionDialog(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Code Page & TLS Row
|
// PDT, Code Page & TLS Row
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.SpaceBetween
|
horizontalArrangement = Arrangement.spacedBy(6.dp)
|
||||||
) {
|
) {
|
||||||
|
var pdtExpanded by remember { mutableStateOf(false) }
|
||||||
|
val pdtList = listOf(
|
||||||
|
"DEFAULT" to "Plain Text",
|
||||||
|
"PCL5" to "HP PCL 5/6",
|
||||||
|
"EPSON_ESC_P" to "Epson ESC/P",
|
||||||
|
"POSTSCRIPT" to "PostScript"
|
||||||
|
)
|
||||||
|
val selectedPdtLabel = pdtList.firstOrNull { it.first == pdtType }?.second ?: "PDT"
|
||||||
|
Box(modifier = Modifier.weight(1f)) {
|
||||||
|
OutlinedButton(
|
||||||
|
onClick = { pdtExpanded = true },
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Text("PDT: $selectedPdtLabel", fontSize = 10.sp, maxLines = 1)
|
||||||
|
}
|
||||||
|
DropdownMenu(
|
||||||
|
expanded = pdtExpanded,
|
||||||
|
onDismissRequest = { pdtExpanded = false },
|
||||||
|
modifier = Modifier.background(Color(0xFF2C2D30))
|
||||||
|
) {
|
||||||
|
pdtList.forEach { (type, label) ->
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = { Text(label, fontSize = 12.sp, color = Color.White) },
|
||||||
|
onClick = {
|
||||||
|
pdtType = type
|
||||||
|
pdtExpanded = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var cpExpanded by remember { mutableStateOf(false) }
|
var cpExpanded by remember { mutableStateOf(false) }
|
||||||
Box {
|
Box(modifier = Modifier.weight(1f)) {
|
||||||
OutlinedButton(onClick = { cpExpanded = true }) {
|
OutlinedButton(
|
||||||
Text("Code Page: CP$codePage", fontSize = 11.sp)
|
onClick = { cpExpanded = true },
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Text("CP$codePage", fontSize = 10.sp, maxLines = 1)
|
||||||
}
|
}
|
||||||
DropdownMenu(
|
DropdownMenu(
|
||||||
expanded = cpExpanded,
|
expanded = cpExpanded,
|
||||||
@@ -346,15 +375,14 @@ fun PrinterSessionDialog(
|
|||||||
modifier = Modifier.clickable { useTls = !useTls }
|
modifier = Modifier.clickable { useTls = !useTls }
|
||||||
) {
|
) {
|
||||||
Checkbox(checked = useTls, onCheckedChange = { useTls = it })
|
Checkbox(checked = useTls, onCheckedChange = { useTls = it })
|
||||||
Spacer(modifier = Modifier.width(4.dp))
|
Text("TLS", fontSize = 11.sp, color = Color.White)
|
||||||
Text("TLS", fontSize = 12.sp, color = Color.White)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Button(
|
Button(
|
||||||
onClick = { startSession() },
|
onClick = { startSession() },
|
||||||
colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF2B8A3E))
|
colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF2B8A3E))
|
||||||
) {
|
) {
|
||||||
Text("Connect Printer")
|
Text("Connect", fontSize = 11.sp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -251,30 +251,7 @@ fun SettingsDialog(
|
|||||||
|
|
||||||
// Setting 6: Default EBCDIC Code Page
|
// Setting 6: Default EBCDIC Code Page
|
||||||
var codePageMenuExpanded by remember { mutableStateOf(false) }
|
var codePageMenuExpanded by remember { mutableStateOf(false) }
|
||||||
val codePagesList = listOf(
|
val codePagesList = haus.nightmare.a3270.storage.HostStorage.AVAILABLE_CODE_PAGES
|
||||||
"037" to "037 - US / Canada / Brazil",
|
|
||||||
"1047" to "1047 - IBM Open Systems / z/OS Unix",
|
|
||||||
"500" to "500 - International Latin-1",
|
|
||||||
"273" to "273 - Germany / Austria",
|
|
||||||
"277" to "277 - Denmark / Norway",
|
|
||||||
"278" to "278 - Sweden / Finland",
|
|
||||||
"280" to "280 - Italy",
|
|
||||||
"284" to "284 - Spain / Latin America",
|
|
||||||
"285" to "285 - United Kingdom",
|
|
||||||
"297" to "297 - France",
|
|
||||||
"870" to "870 - Eastern Europe / Latin-2",
|
|
||||||
"871" to "871 - Iceland",
|
|
||||||
"875" to "875 - Greece (Greek)",
|
|
||||||
"1026" to "1026 - Turkey (Turkish)",
|
|
||||||
"1140" to "1140 - US / Canada (Euro €)",
|
|
||||||
"1141" to "1141 - Germany / Austria (Euro €)",
|
|
||||||
"1148" to "1148 - International (Euro €)",
|
|
||||||
"930" to "930 - Japanese Katakana Mixed DBCS",
|
|
||||||
"939" to "939 - Japanese Latin Mixed DBCS",
|
|
||||||
"935" to "935 - Simplified Chinese Mixed DBCS",
|
|
||||||
"937" to "937 - Traditional Chinese Mixed DBCS",
|
|
||||||
"933" to "933 - Korean Mixed DBCS"
|
|
||||||
)
|
|
||||||
val selectedCpLabel = codePagesList.firstOrNull { it.first == defaultCodePage }?.second ?: "Code Page $defaultCodePage"
|
val selectedCpLabel = codePagesList.firstOrNull { it.first == defaultCodePage }?.second ?: "Code Page $defaultCodePage"
|
||||||
|
|
||||||
Column(modifier = Modifier.fillMaxWidth()) {
|
Column(modifier = Modifier.fillMaxWidth()) {
|
||||||
|
|||||||
Reference in New Issue
Block a user