diff --git a/src/main/kotlin/org/reactome/lit_ball/common/LitBallQuery.kt b/src/main/kotlin/org/reactome/lit_ball/common/LitBallQuery.kt index dcbc6ef..ce97b58 100644 --- a/src/main/kotlin/org/reactome/lit_ball/common/LitBallQuery.kt +++ b/src/main/kotlin/org/reactome/lit_ball/common/LitBallQuery.kt @@ -4,6 +4,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.withContext import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.Serializable import kotlinx.serialization.encodeToString import kotlinx.serialization.json.decodeFromStream import org.reactome.lit_ball.model.AnnotatingRootStore @@ -24,6 +25,12 @@ import java.util.* import kotlin.io.path.Path enum class QueryStatus { UNINITIALIZED, FILTERED2, EXPANDED, FILTERED1 } +@Serializable +enum class Qtype(val pretty: String) { + EXPRESSION_SEARCH("Expression Search"), + SNOWBALLING("Snowballing"), + SUPERVISED_SNOWBALLING("Supervised Snowballing"), +} fun getQueryDir(name: String): File { val queryPath = Settings.map["path-to-queries"] ?: "" @@ -47,6 +54,7 @@ fun getDOIs(dir: File, fileName: String): MutableSet { data class LitBallQuery( var id: Int, val name: String = "", + var type: Qtype = Qtype.SUPERVISED_SNOWBALLING, var status: QueryStatus = QueryStatus.UNINITIALIZED, var setting: QuerySetting? = null, var acceptedSet: MutableSet = mutableSetOf(), diff --git a/src/main/kotlin/org/reactome/lit_ball/common/QueryList.kt b/src/main/kotlin/org/reactome/lit_ball/common/QueryList.kt index 14803ab..3070b28 100644 --- a/src/main/kotlin/org/reactome/lit_ball/common/QueryList.kt +++ b/src/main/kotlin/org/reactome/lit_ball/common/QueryList.kt @@ -42,6 +42,7 @@ object QueryList { newQuery.noNewAccepted = false } } + newQuery.type = newQuery.setting?.type ?: Qtype.SUPERVISED_SNOWBALLING newQuery } } @@ -50,7 +51,7 @@ object QueryList { } fun itemFromId(id: Int?): LitBallQuery? = id?.let { list.find { id == it.id } } - fun addNewItem(name: String, dois: Set) { + fun addNewItem(type: Int, name: String, dois: Set) { val queryDir = getQueryDir(name) if (queryDir.exists()) { handleException(IOException("Directory ${queryDir.absolutePath} already exists. Query not created.")) @@ -71,6 +72,7 @@ object QueryList { list = list.plus( LitBallQuery( id = list.size, + type = Qtype.entries[type], name = name, acceptedSet = dois.toMutableSet() ) diff --git a/src/main/kotlin/org/reactome/lit_ball/common/QuerySetting.kt b/src/main/kotlin/org/reactome/lit_ball/common/QuerySetting.kt index b8591ac..114b087 100644 --- a/src/main/kotlin/org/reactome/lit_ball/common/QuerySetting.kt +++ b/src/main/kotlin/org/reactome/lit_ball/common/QuerySetting.kt @@ -8,13 +8,14 @@ import java.io.File @Serializable data class QuerySetting( + var type: Qtype = Qtype.SUPERVISED_SNOWBALLING, var mandatoryKeyWords: MutableSet = mutableSetOf(), var forbiddenKeyWords: MutableSet = mutableSetOf(), var classifier: String = "virus-EXP", var annotationClasses: MutableSet = mutableSetOf(), ) { override fun toString(): String { - return "QuerySetting(posKeyWords=$mandatoryKeyWords, negKeyWords=$forbiddenKeyWords, classifier=$classifier), annotationClasses=$annotationClasses" + return "QuerySetting(type=${type.name} posKeyWords=$mandatoryKeyWords, negKeyWords=$forbiddenKeyWords, classifier=$classifier), annotationClasses=$annotationClasses" } companion object { diff --git a/src/main/kotlin/org/reactome/lit_ball/dialog/NewItemDialog.kt b/src/main/kotlin/org/reactome/lit_ball/dialog/NewItemDialog.kt index 3de1c34..9cbc02f 100644 --- a/src/main/kotlin/org/reactome/lit_ball/dialog/NewItemDialog.kt +++ b/src/main/kotlin/org/reactome/lit_ball/dialog/NewItemDialog.kt @@ -16,6 +16,7 @@ import androidx.compose.ui.unit.dp import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import org.reactome.lit_ball.common.Qtype import org.reactome.lit_ball.common.QueryList import org.reactome.lit_ball.common.Settings import org.reactome.lit_ball.window.components.Icons @@ -32,6 +33,7 @@ fun NewItemDialog( rootScope: CoroutineScope, onCloseClicked: () -> Unit, ) { + val typeValue = rememberSaveable { mutableStateOf(2) } val fieldValue = rememberSaveable { mutableStateOf("") } val nameValue = rememberSaveable { mutableStateOf("") } val checkValue = rememberSaveable { mutableStateOf(true) } @@ -55,7 +57,7 @@ fun NewItemDialog( checkValue.value = dois.isNotEmpty() && name.isNotEmpty() if (checkValue.value) { rootScope.launch(Dispatchers.IO) { - QueryList.addNewItem(name, dois) + QueryList.addNewItem(typeValue.value, name, dois) } (onCloseClicked)() } @@ -78,6 +80,36 @@ fun NewItemDialog( }, text = { Column(horizontalAlignment = Alignment.Start) { + Row { + Tooltip(text = """ + Available query types are: + 1. Simple expression search: your positive and negative + keyphrases/expressions are sent to Semantic Scholar + for a search over the whole graph. Starting DOIs are + ignored. + 2. Snowballing with automated keyphrase/expression + filtering. No supervised filtering (all matches are + accepted). + 3. (default) Snowballing with automated and supervised + filtering. + """.trimIndent(), + Modifier.align(Alignment.CenterVertically)) { + Icon( + painterResource(Icons.Help), + contentDescription = "Query Settings", + tint = Color.Gray, + modifier = Modifier.size(20.dp) + .align(Alignment.CenterVertically), + ) + } + Spacer(modifier = Modifier.width(14.dp)) + RadioButtonOptions( + Qtype.entries.map { it.pretty }, + typeValue.value, + onOptionSelected = { btn -> typeValue.value = btn} + ) + } + Spacer(modifier = Modifier.height(8.dp)) Row { Tooltip(text = """ Enter name of query. The string is cut off diff --git a/src/main/kotlin/org/reactome/lit_ball/dialog/QuerySettingsDialog.kt b/src/main/kotlin/org/reactome/lit_ball/dialog/QuerySettingsDialog.kt index 084ed2d..5ee0284 100644 --- a/src/main/kotlin/org/reactome/lit_ball/dialog/QuerySettingsDialog.kt +++ b/src/main/kotlin/org/reactome/lit_ball/dialog/QuerySettingsDialog.kt @@ -62,6 +62,7 @@ fun QuerySettingsDialog( item.setting!!.forbiddenKeyWords = StringPatternMatcher.patternSettingFrom(field2Value.value) item.setting!!.classifier = field3Value.value.trim() item.setting!!.annotationClasses = field4Value.value.splitToSet(",") + item.setting!!.type = item.type rootScope.launch(Dispatchers.IO) { item.saveSettings() } diff --git a/src/main/kotlin/org/reactome/lit_ball/window/components/QueryCard.kt b/src/main/kotlin/org/reactome/lit_ball/window/components/QueryCard.kt index 6dfcd21..daad802 100644 --- a/src/main/kotlin/org/reactome/lit_ball/window/components/QueryCard.kt +++ b/src/main/kotlin/org/reactome/lit_ball/window/components/QueryCard.kt @@ -9,10 +9,7 @@ import androidx.compose.material.IconButton import androidx.compose.material.Text import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Settings -import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.ElevatedButton -import androidx.compose.material3.ElevatedCard -import androidx.compose.material3.SuggestionChip +import androidx.compose.material3.* import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -35,6 +32,11 @@ fun QueryCard( onGoClicked: (status: QueryStatus, id: Int) -> Unit, onAnnotateClicked: () -> Unit, ) { + val chipColors = listOf( + Color.hsv(186F, 1F, 1F), + Color.hsv(163f, 1f, 1f), + Color.hsv(0f, 0f, .9f), + ) ElevatedCard { Row(modifier = Modifier.clickable(onClick = onClicked)) { Tooltip("Query-specific settings", Modifier.align(Alignment.CenterVertically)) { @@ -53,19 +55,22 @@ fun QueryCard( } Spacer(modifier = Modifier.width(14.dp)) Column { - SuggestionChip( - onClick = {}, - label = { - Text( - text = AnnotatedString(item.name), - fontWeight = FontWeight.Bold, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - }, - modifier = Modifier - .align(Alignment.Start), - ) + Tooltip("Query Type ${item.type.ordinal+1}", Modifier) { + SuggestionChip( + onClick = {}, + label = { + Text( + text = AnnotatedString(item.name), + fontWeight = FontWeight.Bold, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + }, + modifier = Modifier + .align(Alignment.Start), + colors = SuggestionChipDefaults.suggestionChipColors(containerColor = chipColors[item.type.ordinal]), + ) + } Text( text = "Accepted: ${item.nrAccepted()}", fontSize = 14.sp,