From 295b429d6d2923391b050b3a3fc996cb35ff2c9f Mon Sep 17 00:00:00 2001 From: lunaticbum Date: Wed, 15 Apr 2026 10:08:57 +0900 Subject: [PATCH] ... --- src/main/kotlin/ui/SettingsScreen.kt | 95 ++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/src/main/kotlin/ui/SettingsScreen.kt b/src/main/kotlin/ui/SettingsScreen.kt index b6d7e38..83ea3ed 100644 --- a/src/main/kotlin/ui/SettingsScreen.kt +++ b/src/main/kotlin/ui/SettingsScreen.kt @@ -1,6 +1,8 @@ package ui import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.focusable import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.shape.RoundedCornerShape @@ -10,6 +12,8 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.DragData import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.Color import androidx.compose.ui.onExternalDrag import androidx.compose.ui.text.font.FontWeight @@ -36,6 +40,40 @@ import org.jetbrains.exposed.sql.deleteAll import org.jetbrains.exposed.sql.insert import org.jetbrains.exposed.sql.transactions.transaction import service.SystemSleepPreventer +import java.awt.Toolkit +import java.awt.datatransfer.DataFlavor +import java.io.File +import androidx.compose.ui.input.key.* + +fun getPastedPathFromClipboard(): String? { + val clipboard = Toolkit.getDefaultToolkit().systemClipboard + try { + // 1. 파일 자체가 복사된 경우 (탐색기/파인더에서 Ctrl+C / Cmd+C) + if (clipboard.isDataFlavorAvailable(DataFlavor.javaFileListFlavor)) { + val files = clipboard.getData(DataFlavor.javaFileListFlavor) as? List<*> + val file = files?.firstOrNull() as? File + if (file != null) return file.absolutePath + } + + // 2. 텍스트(경로 문자열)가 복사된 경우 + if (clipboard.isDataFlavorAvailable(DataFlavor.stringFlavor)) { + val text = clipboard.getData(DataFlavor.stringFlavor) as? String + if (text != null) { + // 앞뒤 공백, 따옴표, file:// 접두사 등을 깔끔하게 제거 + var cleanPath = text.trim('"', '\'', ' ', '\n', '\r') + .removePrefix("file://").removePrefix("file:") + // 윈도우 드라이브 문자 앞 슬래시 제거 (예: /C:/ -> C:/) + if (cleanPath.startsWith("/") && cleanPath.getOrNull(2) == ':') { + cleanPath = cleanPath.drop(1) + } + return cleanPath + } + } + } catch (e: Exception) { + e.printStackTrace() + } + return null +} // src/main/kotlin/ui/SettingsScreen.kt @@ -46,6 +84,9 @@ fun SettingsScreen(onAuthSuccess: () -> Unit) { var config by remember { mutableStateOf(KisSession.config) } var statusMessage by remember { mutableStateOf("프로그램 초기화") } + val modelFocusRequester = remember { FocusRequester() } + val embedModelFocusRequester = remember { FocusRequester() } + val authenticateAndStart: suspend () -> Unit = { var retryCount = 0 val maxRetries = 3 @@ -187,6 +228,33 @@ fun SettingsScreen(onAuthSuccess: () -> Unit) { ) { Box( modifier = Modifier.weight(0.5f).height(60.dp).border(1.dp, Color.Gray, RoundedCornerShape(8.dp)) + .focusRequester(modelFocusRequester) + // 2. 포커스 가능하게 설정 + .focusable() + // 3. 클릭하면 해당 박스로 키보드 포커스 이동 + .clickable { modelFocusRequester.requestFocus() } + // 4. 키보드 이벤트 (Ctrl+V / Cmd+V) 감지 + .onKeyEvent { event -> + // 키를 떼는 순간(KeyUp) && 누른 키가 'V' && (Ctrl 혹은 Cmd 키가 눌린 상태) + if (event.type == KeyEventType.KeyUp && + event.key == Key.V && + (event.isCtrlPressed || event.isMetaPressed) + ) { + val rawUri = getPastedPathFromClipboard() + if (rawUri != null && rawUri.endsWith(".gguf", ignoreCase = true)) { + var path = rawUri.removePrefix("file://").removePrefix("file:") + + // 2. 윈도우 환경의 드라이브 문자(예: /C:/) 앞의 슬래시 제거 + if (path.startsWith("/") && path.getOrNull(2) == ':') { + path = path.drop(1) + } + + if (path.endsWith(".gguf")) config = config.copy(modelPath = path) + return@onKeyEvent true // 이벤트 소비 처리 + } + } + false + } .onExternalDrag(onDrop = { state -> val data = state.dragData if (data is DragData.FilesList) { @@ -214,6 +282,33 @@ fun SettingsScreen(onAuthSuccess: () -> Unit) { Box( modifier = Modifier.weight(0.5f).height(60.dp).border(1.dp, Color.Gray, RoundedCornerShape(8.dp)) + .focusRequester(embedModelFocusRequester) + // 2. 포커스 가능하게 설정 + .focusable() + // 3. 클릭하면 해당 박스로 키보드 포커스 이동 + .clickable { embedModelFocusRequester.requestFocus() } + // 4. 키보드 이벤트 (Ctrl+V / Cmd+V) 감지 + .onKeyEvent { event -> + // 키를 떼는 순간(KeyUp) && 누른 키가 'V' && (Ctrl 혹은 Cmd 키가 눌린 상태) + if (event.type == KeyEventType.KeyUp && + event.key == Key.V && + (event.isCtrlPressed || event.isMetaPressed) + ) { + val rawUri = getPastedPathFromClipboard() + if (rawUri != null && rawUri.endsWith(".gguf", ignoreCase = true)) { + var path = rawUri.removePrefix("file://").removePrefix("file:") + + // 2. 윈도우 환경의 드라이브 문자(예: /C:/) 앞의 슬래시 제거 + if (path.startsWith("/") && path.getOrNull(2) == ':') { + path = path.drop(1) + } + + if (path.endsWith(".gguf")) config = config.copy(modelPath = path) + return@onKeyEvent true // 이벤트 소비 처리 + } + } + false + } .onExternalDrag(onDrop = { state -> val data = state.dragData if (data is DragData.FilesList) {