diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/call/ApiCaller.kt b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/call/ApiCaller.kt index 7bc93e0fd..910af4f15 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/call/ApiCaller.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/call/ApiCaller.kt @@ -43,14 +43,20 @@ class ApiCaller { return } - val apiCallUI = actionContext.instance(ApiCallUI::class) - apiCallUI.updateRequestList(requests) - apiCallUI.showUI() - val uiWeakReference = WeakReference(apiCallUI) - project.putUserData(API_CALL_UI, uiWeakReference) - actionContext.on(EventKey.ON_COMPLETED) { - project.putUserData(API_CALL_UI, null) - uiWeakReference.clear() + actionContext.runInSwingUI { + + val apiCallUI = actionContext.instance(ApiCallUI::class) + apiCallUI.updateRequestList(requests) + apiCallUI.showUI() + + actionContext.runAsync { + val uiWeakReference = WeakReference(apiCallUI) + project.putUserData(API_CALL_UI, uiWeakReference) + actionContext.on(EventKey.ON_COMPLETED) { + project.putUserData(API_CALL_UI, null) + uiWeakReference.clear() + } + } } } catch (e: Exception) { logger.traceError("Apis exported failed", e) diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/suv/SuvApiExporter.kt b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/suv/SuvApiExporter.kt index fdef71112..997bd4b8b 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/suv/SuvApiExporter.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/api/export/suv/SuvApiExporter.kt @@ -83,11 +83,11 @@ open class SuvApiExporter { return } - val multipleApiExportDialog = actionContext.instance { SuvApiExportDialog() } + actionContext.runInSwingUI { - UIUtils.show(multipleApiExportDialog) + val multipleApiExportDialog = actionContext.instance { SuvApiExportDialog() } - actionContext.runInSwingUI { + UIUtils.show(multipleApiExportDialog) multipleApiExportDialog.setOnChannelChanged { channel -> if (channel == null) { diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/dialog/ApiCallDialog.form b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/dialog/ApiCallDialog.form deleted file mode 100644 index f1b80b2e0..000000000 --- a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/dialog/ApiCallDialog.form +++ /dev/null @@ -1,456 +0,0 @@ - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
\ No newline at end of file diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/dialog/ApiCallDialog.kt b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/dialog/ApiCallDialog.kt index 54770c721..6f69f5b3a 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/dialog/ApiCallDialog.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/dialog/ApiCallDialog.kt @@ -6,9 +6,14 @@ import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory import com.intellij.openapi.ui.Messages import com.intellij.ui.BooleanTableCellEditor import com.intellij.ui.BooleanTableCellRenderer +import com.intellij.ui.CollectionListModel +import com.intellij.ui.components.JBLabel +import com.intellij.ui.components.JBList import com.intellij.ui.components.JBTabbedPane import com.intellij.ui.table.JBTable import com.intellij.util.ui.ComboBoxCellEditor +import com.intellij.util.ui.JBUI +import com.intellij.util.ui.components.BorderLayoutPanel import com.itangcent.cache.HttpContextCacheHelper import com.itangcent.common.constant.HttpMethod import com.itangcent.common.logger.Log @@ -28,7 +33,6 @@ import com.itangcent.idea.swing.onSelect import com.itangcent.idea.swing.onTextChange import com.itangcent.idea.utils.* import com.itangcent.intellij.extend.rx.ThrottleHelper -import com.itangcent.intellij.extend.rx.throttle import com.itangcent.intellij.extend.withBoundary import com.itangcent.intellij.file.LocalFileRepository import com.itangcent.intellij.psi.PsiClassUtils @@ -38,9 +42,12 @@ import org.apache.commons.lang3.exception.ExceptionUtils import org.apache.http.entity.ContentType import org.jsoup.Jsoup import org.jsoup.nodes.Document +import java.awt.BorderLayout +import java.awt.Dimension +import java.awt.GridBagConstraints +import java.awt.GridBagLayout import java.awt.event.* import java.io.Closeable -import java.util.concurrent.TimeUnit import javax.swing.* import javax.swing.event.TableModelListener import javax.swing.table.DefaultTableModel @@ -49,43 +56,316 @@ import javax.swing.table.TableModel import javax.swing.text.JTextComponent -class ApiCallDialog : ContextDialog(), ApiCallUI { - private lateinit var contentPane: JPanel +@Suppress("DialogTitleCapitalization") +private class ApiCallPanel : BorderLayoutPanel() { + + val searchTextField: JTextField + val apisListPanel: JScrollPane + val apisJList: JBList + + val methodLabel: JLabel + val hostComboBox: JComboBox + val pathTextField: JTextField + + val callButton: JButton + + val requestBodyTextArea: JTextArea + val requestHeadersTextArea: JTextArea + val contentTypeComboBox: JComboBox + val formTable: JBTable + + val requestPanel: JBTabbedPane + + val paramPanel: JPanel + val paramsLabel: JLabel + val paramsTextField: JTextField + + val contentTypePanel: JPanel + val contentTypeLabel: JLabel + + val responsePanel: JBTabbedPane + val formatOrRawButton: JButton + val saveButton: JButton + val statusLabel: JLabel + val responseActionPanel: JPanel + val responseTextArea: JTextArea + val responseHeadersTextArea: JTextArea + + + init { + this.layout = GridBagLayout() // Set the layout manager to GridBagLayout + this.border = BorderFactory.createEmptyBorder(10, 10, 10, 10) + + searchTextField = JTextField() + + // API List Panel + apisJList = JBList() + apisListPanel = JScrollPane(apisJList) + + // Left Panel for Search and API List + val leftPanel = JPanel(BorderLayout()).apply { + // Remove any default margins or padding + border = BorderFactory.createEmptyBorder(0, 0, 0, 0) + } + leftPanel.add(searchTextField, BorderLayout.NORTH) + leftPanel.add(apisListPanel, BorderLayout.CENTER) + + // Add Left Panel to ApiCallPanel + val gbcLeftPanel = GridBagConstraints().apply { + fill = GridBagConstraints.BOTH + gridx = 0 + gridy = 1 + weightx = 0.5 + weighty = 1.0 + } + this.add(leftPanel, gbcLeftPanel) + + // Right Panel + + val rightPanel = JPanel(GridBagLayout()) // Set the layout manager to GridBagLayout + val gbcRightPanel = GridBagConstraints().apply { + fill = GridBagConstraints.BOTH + gridx = 1 + gridy = 0 + gridheight = 2 + weightx = 0.5 + weighty = 1.0 + } + this.add(rightPanel, gbcRightPanel) // Use BorderLayout.EAST as the constraint string + + // Top Panel within Right Panel + methodLabel = JBLabel("GET") + hostComboBox = JComboBox().apply { + isEditable = true // Make it editable + // Optional: Set a maximum width if desired + setMaximumSize(Dimension(250, getPreferredSize().height)) + } + pathTextField = JTextField("path") + callButton = JButton("Call") + + val topPanel = JPanel(GridBagLayout()) + // methodLabel with fixed width + methodLabel.setPreferredSize(Dimension(50, methodLabel.getPreferredSize().height)) // Example width + topPanel.add(methodLabel, GridBagConstraints().apply { + gridx = 0 + gridy = 0 + weightx = 0.0 // No extra space + fill = GridBagConstraints.NONE + }) + + // hostComboBox with fixed width + topPanel.add(hostComboBox, GridBagConstraints().apply { + gridx = 1 + weightx = 0.3 // Adjust this to control the width of the combo box + fill = GridBagConstraints.HORIZONTAL // Make it grow/shrink horizontally as needed + }) + + // pathTextField to fill the rest + topPanel.add(pathTextField, GridBagConstraints().apply { + gridx = 2 + weightx = 1.0 // Take up remaining space + fill = GridBagConstraints.HORIZONTAL + }) + + // callButton with fixed width + callButton.preferredSize = Dimension(80, callButton.getPreferredSize().height) // Example width + topPanel.add(callButton, GridBagConstraints().apply { + gridx = 3 + weightx = 0.0 // No extra space + fill = GridBagConstraints.NONE + }) + + // Add topPanel to rightPanel + rightPanel.add(topPanel, GridBagConstraints().apply { + fill = GridBagConstraints.HORIZONTAL + gridx = 0 + gridy = 0 + weightx = 1.0 // Make it fill the entire width of rightPanel + anchor = GridBagConstraints.LINE_START // Align to the left + insets = JBUI.insetsLeft(8) // Add a 15-pixel margin to the left + }) - private lateinit var apisListPanel: JPanel - private lateinit var apisJList: JList - private lateinit var apisPopMenu: JPopupMenu +// Param Panel within Right Panel + paramsLabel = JBLabel("Params") + paramsTextField = JTextField() + paramPanel = JPanel(GridBagLayout()) // Use GridBagLayout + val gbcParam = GridBagConstraints() + +// paramsLabel with fixed width + gbcParam.gridx = 0 + gbcParam.gridy = 0 + gbcParam.weightx = 0.0 // No extra space + gbcParam.fill = GridBagConstraints.NONE + paramPanel.add(paramsLabel, gbcParam) + +// paramsTextField to fill the rest + gbcParam.gridx = 1 + gbcParam.weightx = 1.0 // Take up remaining space + gbcParam.fill = GridBagConstraints.HORIZONTAL + paramPanel.add(paramsTextField, gbcParam) + + rightPanel.add(paramPanel, GridBagConstraints().apply { + fill = GridBagConstraints.HORIZONTAL + gridy = 1 + weightx = 1.0 + anchor = GridBagConstraints.LINE_START // Align to the left + insets = JBUI.insetsLeft(8) // Add a 15-pixel margin to the left + }) - private lateinit var rightPanel: JPanel +// ContentType Panel within Right Panel + contentTypeLabel = JLabel("ContentType") + contentTypeComboBox = JComboBox() + contentTypePanel = JPanel(GridBagLayout()) // Use GridBagLayout + + // contentTypeLabel with fixed width + contentTypePanel.add(contentTypeLabel, GridBagConstraints().apply { + gridx = 0 + gridy = 0 + weightx = 0.0 // No extra space + fill = GridBagConstraints.NONE + }) - private lateinit var topPanel: JPanel + // contentTypeComboBox to fill the rest + contentTypePanel.add(contentTypeComboBox, GridBagConstraints().apply { + gridx = 1 + weightx = 1.0 // Take up remaining space + fill = GridBagConstraints.HORIZONTAL + }) - private lateinit var callButton: JButton - private lateinit var requestBodyTextArea: JTextArea - private lateinit var responseTextArea: JTextArea - private lateinit var pathTextField: JTextField + rightPanel.add(contentTypePanel, GridBagConstraints().apply { + fill = GridBagConstraints.HORIZONTAL + gridy = 2 + weightx = 1.0 + anchor = GridBagConstraints.LINE_START // Align to the left + insets = JBUI.insetsLeft(8) // Add a 15-pixel margin to the left + }) - private lateinit var methodLabel: JLabel - private lateinit var requestPanel: JBTabbedPane - private lateinit var requestBodyPanel: JPanel - private lateinit var requestHeaderPanel: JPanel - private lateinit var formTable: JBTable + // Request Panel within Right Panel + requestBodyTextArea = JTextArea() + val requestBodyPanel = JScrollPane(requestBodyTextArea) - private lateinit var responsePanel: JBTabbedPane - private lateinit var formatOrRawButton: JButton - private lateinit var saveButton: JButton - private lateinit var responseActionPanel: JPanel - private lateinit var responseHeadersTextArea: JTextArea - private lateinit var requestHeadersTextArea: JTextArea - private lateinit var statusLabel: JLabel + formTable = JBTable() + val formPanel = JScrollPane(formTable) - private lateinit var paramPanel: JPanel - private lateinit var paramsLabel: JLabel - private lateinit var paramsTextField: JTextField + requestHeadersTextArea = JTextArea() + val requestHeaderPanel = JScrollPane(requestHeadersTextArea) - private lateinit var contentTypePanel: JPanel - private lateinit var contentTypeLabel: JLabel - private lateinit var contentTypeComboBox: JComboBox + requestPanel = JBTabbedPane().apply { + addTab("Body", requestBodyPanel) + addTab("Form", formPanel) + addTab("Headers", requestHeaderPanel) + } + + rightPanel.add(requestPanel, GridBagConstraints().apply { + fill = GridBagConstraints.BOTH + gridy = 3 + weightx = 1.0 + weighty = 1.0 + }) + + // Response Panel within Right Panel + responseTextArea = JTextArea() + formatOrRawButton = JButton("format") + statusLabel = JBLabel("status: unknown") + saveButton = JButton("save") + responseActionPanel = JPanel().apply { + layout = BoxLayout(this, BoxLayout.Y_AXIS) + add(formatOrRawButton) + add(statusLabel) + add(saveButton) + // Add a vertical glue to push all components to the top + add(Box.createVerticalGlue()) + } + + + val responseBodyPanel = JPanel(GridBagLayout()).apply { + add(JScrollPane(responseTextArea), GridBagConstraints().apply { + fill = GridBagConstraints.BOTH + gridx = 0 + weightx = 1.0 + weighty = 1.0 + }) + add(responseActionPanel, GridBagConstraints().apply { + fill = GridBagConstraints.VERTICAL + gridx = 1 + weightx = 0.1 + weighty = 1.0 + anchor = GridBagConstraints.NORTH + }) + } + + responseHeadersTextArea = JTextArea().apply { isEditable = false } + val responseHeaderPanel = JScrollPane(responseHeadersTextArea) + + responsePanel = JBTabbedPane().apply { + addTab("Body", responseBodyPanel) + addTab("Headers", responseHeaderPanel) + } + rightPanel.add(responsePanel, GridBagConstraints().apply { + fill = GridBagConstraints.BOTH + gridy = 4 + weightx = 1.0 + weighty = 1.0 + }) + } + +} + +class ApiCallDialog : ContextDialog(), ApiCallUI { + private val apiCallPanel = ApiCallPanel() + + private val searchTextField: JTextField + get() = apiCallPanel.searchTextField + + private val apisJList: JList + get() = apiCallPanel.apisJList + + private val callButton: JButton + get() = apiCallPanel.callButton + private val requestBodyTextArea: JTextArea + get() = apiCallPanel.requestBodyTextArea + private val responseTextArea: JTextArea + get() = apiCallPanel.responseTextArea + private val pathTextField: JTextField + get() = apiCallPanel.pathTextField + + private val methodLabel: JLabel + get() = apiCallPanel.methodLabel + private val requestPanel: JBTabbedPane + get() = apiCallPanel.requestPanel + private val formTable: JBTable + get() = apiCallPanel.formTable + + private val formatOrRawButton: JButton + get() = apiCallPanel.formatOrRawButton + private val saveButton: JButton + get() = apiCallPanel.saveButton + private val responseActionPanel: JPanel + get() = apiCallPanel.responseActionPanel + + private val responseHeadersTextArea: JTextArea + get() = apiCallPanel.responseHeadersTextArea + private val requestHeadersTextArea: JTextArea + get() = apiCallPanel.requestHeadersTextArea + private val statusLabel: JLabel + get() = apiCallPanel.statusLabel + + private val paramPanel: JPanel + get() = apiCallPanel.paramPanel + private val paramsLabel: JLabel + get() = apiCallPanel.paramsLabel + private val paramsTextField: JTextField + get() = apiCallPanel.paramsTextField + + private val contentTypePanel: JPanel + get() = apiCallPanel.contentTypePanel + private val contentTypeLabel: JLabel + get() = apiCallPanel.contentTypeLabel + private val contentTypeComboBox: JComboBox + get() = apiCallPanel.contentTypeComboBox + + private val hostComboBox: JComboBox + get() = apiCallPanel.hostComboBox // private val autoComputer: AutoComputer = AutoComputer() @@ -98,11 +378,9 @@ class ApiCallDialog : ContextDialog(), ApiCallUI { private var currUrl: String? = null - private var hostComboBox: JComboBox? = null private val requestRawInfoBinderFactory: DbBeanBinderFactory by lazy { - DbBeanBinderFactory(projectCacheRepository!!.getOrCreateFile(".api.call.v1.0.db").path) - { NULL_REQUEST_INFO_CACHE } + DbBeanBinderFactory(projectCacheRepository!!.getOrCreateFile(".api.call.v1.0.db").path) { NULL_REQUEST_INFO_CACHE } } private val throttleHelper = ThrottleHelper() @@ -113,7 +391,13 @@ class ApiCallDialog : ContextDialog(), ApiCallUI { init { LOG.info("create ApiCallDialog") - setContentPane(contentPane) + + // Setting preferred, maximum, and minimum sizes + this.preferredSize = Dimension(900, 600) + this.maximumSize = Dimension(1200, 800) + this.minimumSize = Dimension(600, 400) + + contentPane = apiCallPanel getRootPane().defaultButton = callButton contentTypeComboBox.model = DefaultComboBoxModel(CONTENT_TYPES) @@ -127,14 +411,12 @@ class ApiCallDialog : ContextDialog(), ApiCallUI { }) // call onCancel() on ESCAPE - contentPane.registerKeyboardAction( - { onCancel() }, - KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), - JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT + apiCallPanel.registerKeyboardAction( + { onCancel() }, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT ) // call onCallClick() on ENTER - contentPane.registerKeyboardAction( + apiCallPanel.registerKeyboardAction( { onCallClick() }, KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT @@ -175,11 +457,17 @@ class ApiCallDialog : ContextDialog(), ApiCallUI { formatOrRawButton.isFocusPainted = false saveButton.isFocusPainted = false - SwingUtils.underLine(this.hostComboBox!!) + SwingUtils.underLine(this.hostComboBox) SwingUtils.underLine(this.pathTextField) SwingUtils.underLine(this.paramsTextField) EasyIcons.Run.iconOnly(this.callButton) + + SearchSupport.bindSearch( + searchInputField = searchTextField, + sourceList = { apiModelList }, + uiList = apisJList + ) } override fun init() { @@ -201,7 +489,7 @@ class ApiCallDialog : ContextDialog(), ApiCallUI { } } - apisPopMenu = JPopupMenu() + val apisPopMenu = JPopupMenu() val resetItem = JMenuItem("Reset") @@ -237,10 +525,12 @@ class ApiCallDialog : ContextDialog(), ApiCallUI { this.contentTypeComboBox.selectedItem = currRequest.contentType() updateResponse(null) this.responseTextArea.text = - apiList?.get(selectedIndex)?.origin - ?.response?.firstOrNull()?.body?.let { RequestUtils.parseRawBody(it) } + apiList?.get(selectedIndex)?.origin?.response?.firstOrNull()?.body?.let { RequestUtils.parseRawBody(it) } ?: "" formatForm(currRequest) + + apiCallPanel.revalidate() + apiCallPanel.repaint() } override fun updateRequestList(requestList: List?) { @@ -248,26 +538,29 @@ class ApiCallDialog : ContextDialog(), ApiCallUI { return } doAfterInit { - val requestRawList = ArrayList(requestList.size) - val requestRawViewList = ArrayList(requestList.size) - requestList.forEach { request -> - val rawInfo = rawInfo(request) - requestRawList.add(ApiInfo(request, rawInfo)) - val beanBinder = this.requestRawInfoBinderFactory.getBeanBinder(rawInfo.cacheKey()) - requestRawViewList.add(beanBinder.tryRead() ?: rawInfo.copy()) - } - this.apiList = requestRawList - this.requestRawViewList = requestRawViewList - this.apisJList.model = DefaultComboBoxModel( - List(requestRawViewList.size) { index: Int -> RequestNameWrapper(index) } - .toTypedArray() - ) - if (requestRawViewList.isNotEmpty()) { - this.apisJList.selectedIndex = 0 + actionContext.runInSwingUI { + val requestRawList = ArrayList(requestList.size) + val requestRawViewList = ArrayList(requestList.size) + requestList.forEach { request -> + val rawInfo = rawInfo(request) + requestRawList.add(ApiInfo(request, rawInfo)) + val beanBinder = this.requestRawInfoBinderFactory.getBeanBinder(rawInfo.cacheKey()) + requestRawViewList.add(beanBinder.tryRead() ?: rawInfo.copy()) + } + this.apiList = requestRawList + this.requestRawViewList = requestRawViewList + this.apisJList.model = CollectionListModel(apiModelList) + if (requestRawViewList.isNotEmpty()) { + this.apisJList.selectedIndex = 0 + } } } } + private val apiModelList: List + get() = requestRawViewList?.let { List(it.size) { index: Int -> RequestNameWrapper(index) } } + ?: emptyList() + private fun resetCurrentRequestView() { val index = this.apisJList.selectedIndex if (index < 0) { @@ -278,8 +571,7 @@ class ApiCallDialog : ContextDialog(), ApiCallUI { val requestView = requestRawInfo.copy() (this.requestRawViewList as MutableList)[index] = requestView changeRequest() - requestRawInfoBinderFactory.getBeanBinder(requestRawInfo.cacheKey()) - .save(null) + requestRawInfoBinderFactory.getBeanBinder(requestRawInfo.cacheKey()).save(null) this.apisJList.repaint() } @@ -306,12 +598,9 @@ class ApiCallDialog : ContextDialog(), ApiCallUI { httpClient = httpClientProvider!!.getHttpClient() try { - httpContextCacheHelper.getCookies() - .asSequence() - .filter { it.getName().notNullOrEmpty() } - .forEach { - httpClient!!.cookieStore().addCookie(it) - } + httpContextCacheHelper.getCookies().asSequence().filter { it.getName().notNullOrEmpty() }.forEach { + httpClient!!.cookieStore().addCookie(it) + } } catch (e: Exception) { logger.traceError("load cookie failed!", e) } @@ -321,24 +610,20 @@ class ApiCallDialog : ContextDialog(), ApiCallUI { private fun initRequestModule() { - (this.requestPanel.getTabComponentAt(0) as? JLabel) - ?.listenModify(requestBodyTextArea) { - selectedRequestRawInfo()?.body - } + (this.requestPanel.getTabComponentAt(0) as? JLabel)?.listenModify(requestBodyTextArea) { + selectedRequestRawInfo()?.body + } - (this.requestPanel.getTabComponentAt(2) as? JLabel) - ?.listenModify(requestHeadersTextArea) { - selectedRequestRawInfo()?.headers - } + (this.requestPanel.getTabComponentAt(2) as? JLabel)?.listenModify(requestHeadersTextArea) { + selectedRequestRawInfo()?.headers + } this.paramsLabel.listenModify(this.paramsTextField) { selectedRequestRawInfo()?.querys } this.requestHeadersTextArea.onTextChange { header -> - if (contentTypeChangeThrottle.acquire(500) - && requestChangeThrottle.acquire(100) - ) { + if (contentTypeChangeThrottle.acquire(500) && requestChangeThrottle.acquire(100)) { this.currRequest?.headers = header this.currRequest?.contentType()?.let { if (this.contentTypeComboBox.selectedItem != it) { @@ -400,20 +685,15 @@ class ApiCallDialog : ContextDialog(), ApiCallUI { return header } else { found = true - newHeader.appendlnIfNotEmpty() - .append("Content-Type=") - .append(contentType) + newHeader.appendlnIfNotEmpty().append("Content-Type=").append(contentType) } } else { - newHeader.appendlnIfNotEmpty() - .append(line) + newHeader.appendlnIfNotEmpty().append(line) } } } if (!found) { - newHeader.appendlnIfNotEmpty() - .append("Content-Type=") - .append(contentType) + newHeader.appendlnIfNotEmpty().append("Content-Type=").append(contentType) } return newHeader.toString() } @@ -423,8 +703,7 @@ class ApiCallDialog : ContextDialog(), ApiCallUI { if (request.querys.isNullOrEmpty()) { return "" } - val path = StringBuilder() - .append("?") + val path = StringBuilder().append("?") request.querys!!.forEach { param -> if (path.lastOrNull() != '?') { path.append("&") @@ -816,12 +1095,10 @@ class ApiCallDialog : ContextDialog(), ApiCallUI { if (apiCallDialog!!.throttleHelper.acquire("select_file_for_form_param", 1000)) { IdeaFileChooserHelper.create( - apiCallDialog!!.actionContext, - FileChooserDescriptorFactory.createSingleFileDescriptor() - ).lastSelectedLocation("file.form.param.select.last.location.key") - .selectFile { - formTable.setValueAt(it?.path, row, column) - } + apiCallDialog!!.actionContext, FileChooserDescriptorFactory.createSingleFileDescriptor() + ).lastSelectedLocation("file.form.param.select.last.location.key").selectFile { + formTable.setValueAt(it?.path, row, column) + } } formTable.selectionModel.clearSelection() } @@ -842,10 +1119,7 @@ class ApiCallDialog : ContextDialog(), ApiCallUI { if (request?.headers.isNullOrEmpty()) return "" val sb = StringBuilder() request?.headers?.forEach { - sb.append(it.name) - .append("=") - .append(it.value ?: "") - .appendLine() + sb.append(it.name).append("=").append(it.value ?: "").appendLine() } return sb.toString() } @@ -859,7 +1133,7 @@ class ApiCallDialog : ContextDialog(), ApiCallUI { actionContext.withBoundary { val hosts = httpContextCacheHelper.getHosts() actionContext.runInSwingUI { - this.hostComboBox!!.model = DefaultComboBoxModel(hosts.toTypedArray()) + this.hostComboBox.model = DefaultComboBoxModel(hosts.toTypedArray()) } } } @@ -871,7 +1145,7 @@ class ApiCallDialog : ContextDialog(), ApiCallUI { } refreshDataFromUI() val request = currRequest!! - val host = this.hostComboBox!!.editor.item as String + val host = this.hostComboBox.editor.item as String val path = this.pathTextField.text val query = this.paramsTextField.text @@ -882,12 +1156,9 @@ class ApiCallDialog : ContextDialog(), ApiCallUI { onNewHost(host) var url: String? = null try { - url = RequestUtils.UrlBuild().host(host) - .path(path) - .query(query).url() + url = RequestUtils.UrlBuild().host(host).path(path).query(query).url() this.currUrl = url - val httpRequest = getHttpClient().request().method(request.method ?: "GET") - .url(url) + val httpRequest = getHttpClient().request().method(request.method ?: "GET").url(url) if (requestHeader.notNullOrBlank()) { parseEqualLine(requestHeader) { name, value -> @@ -921,8 +1192,7 @@ class ApiCallDialog : ContextDialog(), ApiCallUI { } } if (request.body != null) { - httpRequest.contentType(ContentType.APPLICATION_JSON) - .body(requestBodyOrForm) + httpRequest.contentType(ContentType.APPLICATION_JSON).body(requestBodyOrForm) } } @@ -935,13 +1205,14 @@ class ApiCallDialog : ContextDialog(), ApiCallUI { } catch (e: Exception) { actionContext.runInSwingUI { - responseTextArea.text = "Could not get any response" + - "\nThere was an error connecting:" + url + - "\nThe stackTrace is:" + - ExceptionUtils.getStackTrace(e) + responseTextArea.text = + "Could not get any response" + "\nThere was an error connecting:" + url + "\nThe stackTrace is:" + ExceptionUtils.getStackTrace( + e + ) } } } + } //endregion @@ -964,10 +1235,7 @@ class ApiCallDialog : ContextDialog(), ApiCallUI { if (response?.headers() == null) return "" val sb = StringBuilder() response.headers()?.forEach { - sb.append(it.name()) - .append("=") - .append(it.value()) - .appendLine() + sb.append(it.name()).append("=").append(it.value()).appendLine() } return sb.toString() } @@ -981,8 +1249,7 @@ class ApiCallDialog : ContextDialog(), ApiCallUI { refreshDataFromUI() if (this.currResponse == null) { Messages.showMessageDialog( - this, "No Response", - "Error", Messages.getErrorIcon() + this, "No Response", "Error", Messages.getErrorIcon() ) return } @@ -991,8 +1258,7 @@ class ApiCallDialog : ContextDialog(), ApiCallUI { val bytes = response.bytes() if (bytes == null) { Messages.showMessageDialog( - this, "Response is empty", - "Error", Messages.getErrorIcon() + this, "Response is empty", "Error", Messages.getErrorIcon() ) return } @@ -1042,8 +1308,7 @@ class ApiCallDialog : ContextDialog(), ApiCallUI { httpClient?.cookieStore()?.cookies()?.let { httpContextCacheHelper.addCookies(it) } this.requestRawViewList?.forEachIndexed { index, requestRawInfo -> if (requestRawInfo != this.apiList!![index].raw) { - this.requestRawInfoBinderFactory.getBeanBinder(requestRawInfo.cacheKey()) - .save(requestRawInfo) + this.requestRawInfoBinderFactory.getBeanBinder(requestRawInfo.cacheKey()).save(requestRawInfo) } } (httpClient as? Closeable)?.close() @@ -1077,9 +1342,7 @@ class ApiCallDialog : ContextDialog(), ApiCallUI { return try { val contentType = safe { response.contentType()?.let { ContentType.parse(it) } } if (contentType != null) { - if (contentType.mimeType.startsWith("text/html") || - contentType.mimeType.startsWith("text/xml") - ) { + if (contentType.mimeType.startsWith("text/html") || contentType.mimeType.startsWith("text/xml")) { val doc: Document = Jsoup.parse(getRawResult()) doc.outputSettings().prettyPrint(true) return doc.outerHtml() @@ -1169,12 +1432,9 @@ class ApiCallDialog : ContextDialog(), ApiCallUI { if (this.headers.isNullOrEmpty()) { return null } - val lowerName = name.toLowerCase() - return parseEqualLine(this.headers!!) { k, v -> k to v } - .asSequence() - .filter { it.first.toLowerCase() == lowerName } - .map { it.second } - .firstOrNull() + val lowerName = name.lowercase() + return parseEqualLine(this.headers!!) { k, v -> k to v }.asSequence() + .filter { it.first.lowercase() == lowerName }.map { it.second }.firstOrNull() } fun copy(): RequestRawInfo { @@ -1244,8 +1504,7 @@ class ApiCallDialog : ContextDialog(), ApiCallUI { val requestRawInfo = RequestRawInfo() requestRawInfo.key = actionContext.callInReadUI { PsiClassUtils.fullNameOfMember( - request.resourceClass(), - request.resourceMethod()!! + request.resourceClass(), request.resourceMethod()!! ) } requestRawInfo.name = request.name?.trim() @@ -1262,11 +1521,7 @@ class ApiCallDialog : ContextDialog(), ApiCallUI { companion object : Log() { var CONTENT_TYPES: Array = arrayOf( - "", - "application/json", - "application/x-www-form-urlencoded", - "multipart/form-data", - "application/xml" + "", "application/json", "application/x-www-form-urlencoded", "multipart/form-data", "application/xml" ) val disabledFormTableBinder: FormTableBinder = DisabledFormTableBinder() diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/dialog/ChooseWithTipDialog.form b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/dialog/ChooseWithTipDialog.form deleted file mode 100644 index f7aaaf0b9..000000000 --- a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/dialog/ChooseWithTipDialog.form +++ /dev/null @@ -1,96 +0,0 @@ - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/dialog/ChooseWithTipDialog.kt b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/dialog/ChooseWithTipDialog.kt index 94372c0d7..d6e9b2326 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/dialog/ChooseWithTipDialog.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/dialog/ChooseWithTipDialog.kt @@ -1,61 +1,87 @@ package com.itangcent.idea.plugin.dialog import com.intellij.ui.MutableCollectionComboBoxModel +import com.intellij.ui.components.JBScrollPane import com.itangcent.common.utils.notNullOrEmpty import com.itangcent.idea.utils.SwingUtils -import java.awt.EventQueue -import java.awt.Window +import java.awt.* import java.awt.event.KeyEvent import java.awt.event.WindowAdapter import java.awt.event.WindowEvent import javax.swing.* +import kotlin.math.max class ChooseWithTipDialog(owner: Window? = null) : JDialog(owner) { - private var contentPane: JPanel? = null - private var messageLabel: JLabel? = null - private var buttonOK: JButton? = null - private var buttonCancel: JButton? = null - private var itemComboBox: JComboBox? = null - private var itemTip: JTextArea? = null - private var items: List? = null - private var tipAs: ((T) -> String?)? = null - private var callBack: ((T?) -> Unit)? = null + + private val contentPane = JPanel(BorderLayout()) + private val messageLabel = JLabel("Label") + private val itemComboBox = JComboBox() + private val itemTip = JTextArea() + private val buttonOK = JButton("OK") + private val buttonCancel = JButton("Cancel") + + private var items: List = emptyList() + private var tipAs: ((T) -> String) = { it.toString() } + private var callBack: ((T?) -> Unit) = {} fun updateItems( message: String?, - items: List?, + items: List, showAs: ((T) -> String?)?, - tipAs: ((T) -> String?)?, + tipAs: ((T) -> String)?, callBack: ((T?) -> Unit), ) { this.items = items - this.tipAs = tipAs + if (tipAs != null) { + this.tipAs = tipAs + } this.callBack = callBack - val showValues = items?.map(showAs ?: { it.toString() }) ?: emptyList() + val showValues = items.map(showAs ?: { it.toString() }) EventQueue.invokeLater { if (message.isNullOrBlank()) { - messageLabel!!.isVisible = false + messageLabel.isVisible = false } else { - messageLabel!!.text = message + messageLabel.text = message } if (showValues.notNullOrEmpty()) { - itemComboBox!!.model = + itemComboBox.model = MutableCollectionComboBoxModel(showValues) - itemComboBox!!.selectedIndex = 0 + itemComboBox.selectedIndex = 0 } } + + val widthForShowItems = showValues.asSequence() + .filterNotNull() + .map { it.length } + .maxOrNull()?.let { it * 10 } ?: 0 + val tips = items.map(this.tipAs).map { it.split("\n") } + val heightForTips = tips.asSequence() + .map { it.size } + .maxOrNull()?.let { it * 20 } ?: 0 + val widthForTips = tips.asSequence() + .flatMap { it.asSequence() } + .map { it.length } + .maxOrNull()?.let { it * 10 } ?: 0 + EventQueue.invokeLater { + this.size = Dimension( + max(widthForShowItems, widthForTips) + .coerceIn(200, 500), + heightForTips + .coerceIn(200, 500) + ) + } } private fun onOK() { - val selectedItem = itemComboBox!!.selectedIndex.takeIf { it != -1 } - ?.let { items!![it] } + val selectedItem = itemComboBox.selectedIndex.takeIf { it != -1 } + ?.let { items[it] } dispose() - callBack!!(selectedItem) + callBack(selectedItem) } private fun onCancel() { dispose() - callBack!!(null) + callBack(null) } private fun close() { @@ -63,13 +89,36 @@ class ChooseWithTipDialog(owner: Window? = null) : JDialog(owner) { } init { + // Top Section + contentPane.add(messageLabel, BorderLayout.NORTH) + + // Middle Section + val middlePanel = JPanel(BorderLayout()) + middlePanel.add(itemComboBox, BorderLayout.NORTH) + val scrollPane = JBScrollPane(itemTip) + middlePanel.add(scrollPane, BorderLayout.CENTER) + contentPane.add(middlePanel, BorderLayout.CENTER) + + // Bottom Section + val bottomPanel = JPanel(FlowLayout(FlowLayout.RIGHT)) + bottomPanel.add(buttonOK) + bottomPanel.add(buttonCancel) + contentPane.add(bottomPanel, BorderLayout.SOUTH) + + pack() + setLocationRelativeTo(null) // Center the dialog on the screen + + // Action Listeners + buttonOK.addActionListener { onOK() } + buttonCancel.addActionListener { onCancel() } + setContentPane(contentPane) isModal = true getRootPane().defaultButton = buttonOK SwingUtils.centerWindow(this) - buttonOK!!.addActionListener { onOK() } - buttonCancel!!.addActionListener { onCancel() } + buttonOK.addActionListener { onOK() } + buttonCancel.addActionListener { onCancel() } // call onCancel() when cross is clicked defaultCloseOperation = DO_NOTHING_ON_CLOSE @@ -80,24 +129,27 @@ class ChooseWithTipDialog(owner: Window? = null) : JDialog(owner) { }) // call onCancel() on ESCAPE - contentPane!!.registerKeyboardAction({ onCancel() }, + contentPane.registerKeyboardAction( + { onCancel() }, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), - JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) + JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT + ) - itemComboBox?.addActionListener { + itemComboBox.addActionListener { onItemSelected() } } private fun onItemSelected() { - val tip = itemComboBox!!.selectedIndex.takeIf { it != -1 } - .takeIf { tipAs != null } - ?.let { items!![it] }?.let { tipAs!!(it) } + val tip = itemComboBox.selectedIndex.takeIf { it != -1 } + ?.let { items[it] } + ?.let { tipAs(it) } if (tip.isNullOrBlank()) { - itemTip!!.isVisible = false + itemTip.isVisible = false + itemTip.text = "" return } - itemTip!!.isVisible = true - itemTip!!.text = tip + itemTip.isVisible = true + itemTip.text = tip } } \ No newline at end of file diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/dialog/SearchSupport.kt b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/dialog/SearchSupport.kt new file mode 100644 index 000000000..36a814433 --- /dev/null +++ b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/dialog/SearchSupport.kt @@ -0,0 +1,102 @@ +package com.itangcent.idea.plugin.dialog + +import com.intellij.ui.CollectionListModel +import javax.swing.JList +import javax.swing.JTextField +import javax.swing.Timer +import javax.swing.event.DocumentEvent +import javax.swing.event.DocumentListener + +/** + * @author joe.wu + * @date 2023/11/13 7:58am + */ +object SearchSupport { + + val BASIC_MATCHER: Matcher = { search, item -> + item.toString().contains(search, true) + } + + val ENHANCED_MATCHER: Matcher = { search, item -> + isSubsequence(search, item.toString()) + } + + private fun isSubsequence(s: String, t: String): Boolean { + var indexS = 0 + var indexT = 0 + while (indexS < s.length && indexT < t.length) { + if (s[indexS].equals(t[indexT], ignoreCase = true)) { + indexS++ + } + indexT++ + } + return indexS == s.length + } + + @Suppress("SYNTHETIC_SETTER_PROJECTED_OUT") + fun bindSearch( + searchInputField: JTextField, + sourceList: () -> List<*>, + uiList: JList<*>, + match: (String, Any) -> Boolean = ENHANCED_MATCHER, + onSearch: (String) -> Unit = {} + ) { + // Keep track of the previous text to avoid unnecessary updates + var previousText: String? = null + + fun updateList() { + val source = sourceList() + val query = searchInputField.text + + if (query == previousText) { + return + } + + previousText = query + + // Remember the currently selected items + val selectedItems = uiList.selectedValuesList + + val newModel = if (query.isNullOrEmpty()) { + CollectionListModel(source) + } else { + val filtered = source.asSequence().filterNotNull().filter { match(query, it) }.toList() + CollectionListModel(filtered) + } + + uiList.model = newModel + + // Restore the selection + val indicesToSelect = mutableListOf() + for (selectedItem in selectedItems) { + val index = newModel.items.indexOf(selectedItem) + if (index != -1) { + indicesToSelect.add(index) + } + } + + val selectedIndices = indicesToSelect.toIntArray() + uiList.selectedIndices = selectedIndices + + onSearch(query) + } + + val timer = Timer(600) { updateList() } + + searchInputField.document.addDocumentListener(object : DocumentListener { + override fun insertUpdate(e: DocumentEvent) { + timer.restart() + } + + override fun removeUpdate(e: DocumentEvent) { + timer.restart() + } + + override fun changedUpdate(e: DocumentEvent) { + timer.restart() + } + }) + } +} + +typealias Matcher = (String, Any) -> Boolean \ No newline at end of file diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/dialog/SuvApiExportDialog.form b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/dialog/SuvApiExportDialog.form deleted file mode 100644 index 62ff91158..000000000 --- a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/dialog/SuvApiExportDialog.form +++ /dev/null @@ -1,132 +0,0 @@ - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/dialog/SuvApiExportDialog.kt b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/dialog/SuvApiExportDialog.kt index ccda16a3b..9af8feab3 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/dialog/SuvApiExportDialog.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/dialog/SuvApiExportDialog.kt @@ -1,7 +1,10 @@ package com.itangcent.idea.plugin.dialog import com.intellij.ide.util.PropertiesComponent +import com.intellij.openapi.ui.ComboBox import com.intellij.ui.components.JBCheckBox +import com.intellij.ui.components.JBList +import com.intellij.util.ui.components.BorderLayoutPanel import com.itangcent.common.logger.traceError import com.itangcent.common.utils.GsonUtils import com.itangcent.common.utils.notNullOrEmpty @@ -15,112 +18,162 @@ import java.awt.event.WindowEvent import java.awt.event.WindowFocusListener import javax.swing.* + +private class SuvApiExportPanel : BorderLayoutPanel() { + + val searchInputField = JTextField().apply { + minimumSize = Dimension(100, 30) + } + val selectAllCheckBox = JBCheckBox() + val channelComboBox = ComboBox() + val buttonOK = JButton("✔").apply { + preferredSize = Dimension(40, 30) + minimumSize = Dimension(40, 30) + } + val buttonCancel = JButton("X").apply { + preferredSize = Dimension(40, 30) + minimumSize = Dimension(40, 30) + } + + val apiList = JBList() + + init { + // Top Panel + val topPanel = JPanel().apply { + layout = BoxLayout(this, BoxLayout.X_AXIS) + add(searchInputField) // Add search input field at the top and left + add(selectAllCheckBox) + add(channelComboBox) + add(Box.createHorizontalGlue()) // To push the following components to the right + add(buttonOK) + add(buttonCancel) + } + + // Center Panel + val centerPanel = JScrollPane(apiList) + + // Setup BorderLayoutPanel + addToTop(topPanel) + addToCenter(centerPanel) + } +} + + class SuvApiExportDialog : ContextDialog() { companion object { private const val LAST_USED_CHANNEL = "com.itangcent.easyapi.suv.last.used.channel" } - private var contentPane: JPanel? = null - private var buttonOK: JButton? = null - private var buttonCancel: JButton? = null - private var channelComboBox: JComboBox<*>? = null + private var trigger = TriggerSupport() - private var apiList: JList<*>? = null - private var docList: List<*>? = null + private val suvApiExportPanel = SuvApiExportPanel() + private val buttonOK get() = suvApiExportPanel.buttonOK + private val buttonCancel get() = suvApiExportPanel.buttonCancel + private val channelComboBox get() = suvApiExportPanel.channelComboBox + private val apiList get() = suvApiExportPanel.apiList + private val selectAllCheckBox get() = suvApiExportPanel.selectAllCheckBox + private val searchInputField get() = suvApiExportPanel.searchInputField - private var selectAllCheckBox: JBCheckBox? = null + private var docList: List<*>? = null private var apisHandle: ((Any?, List<*>) -> Unit)? = null private var onChannelChanged: ((Any?) -> Unit)? = null init { - this.isUndecorated = false + minimumSize = Dimension(400, 400) maximumSize = Dimension(800, 800) - //this.isResizable = false - setContentPane(contentPane) - isModal = false + contentPane = suvApiExportPanel getRootPane().defaultButton = buttonOK SwingUtils.centerWindow(this) - buttonOK!!.addActionListener { onOK() } + buttonOK.addActionListener { onOK() } - buttonCancel!!.addActionListener { onCancel() } + buttonCancel.addActionListener { onCancel() } - // call onCancel() when cross is clicked - defaultCloseOperation = WindowConstants.DO_NOTHING_ON_CLOSE addWindowListener(object : WindowAdapter() { override fun windowClosing(e: WindowEvent?) { onCancel() } }) - SwingUtils.immersed(this.channelComboBox!!) + SwingUtils.immersed(this.channelComboBox) EasyIcons.Close.iconOnly(this.buttonCancel) EasyIcons.OK.iconOnly(this.buttonOK) // call onCancel() on ESCAPE - contentPane!!.registerKeyboardAction( + suvApiExportPanel.registerKeyboardAction( { onCancel() }, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT ) // call onCallClick() on ENTER - contentPane!!.registerKeyboardAction( + suvApiExportPanel.registerKeyboardAction( { onOK() }, KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT ) - selectAllCheckBox!!.addChangeListener { + selectAllCheckBox.addChangeListener { onSelectedAll() } - channelComboBox!!.addActionListener { - onChannelChanged?.invoke(channelComboBox?.selectedItem) + channelComboBox.addActionListener { + onChannelChanged?.invoke(channelComboBox.selectedItem) + } + + SearchSupport.bindSearch( + searchInputField = searchInputField, + sourceList = { docList!! }, + uiList = apiList + ) + + apiList.addListSelectionListener { + this.trigger.withTrigger("onSelect") { + selectAllCheckBox.isSelected = apiList.model.size == apiList.selectionModel.selectedItemsCount + } } } - private fun onSelectedAll() { - if (selectAllCheckBox!!.isSelected) { - apiList!!.selectionModel!!.addSelectionInterval(0, docList!!.size) + private fun onSelectedAll() = this.trigger.withTrigger("onSelectAll") { + if (selectAllCheckBox.isSelected) { + apiList.selectionModel!!.addSelectionInterval(0, docList!!.size) } else { - apiList!!.selectionModel!!.clearSelection() + apiList.selectionModel!!.clearSelection() } } fun updateRequestList(requestList: List<*>) { this.docList = requestList - this.apiList!!.model = DefaultComboBoxModel(requestList.toTypedArray()) + this.apiList.model = DefaultComboBoxModel(requestList.toTypedArray()) } fun selectAll() { - this.selectAllCheckBox!!.isSelected = true - onSelectedAll() + this.selectAllCheckBox.isSelected = true } fun selectMethod(api: Any?) { - this.selectAllCheckBox!!.isSelected = false + this.selectAllCheckBox.isSelected = false this.docList?.indexOf(api)?.let { - apiList!!.selectedIndex = it + apiList.selectedIndex = it } } fun setChannels(channels: List<*>) { - this.channelComboBox!!.model = DefaultComboBoxModel(channels.toTypedArray()) + this.channelComboBox.model = DefaultComboBoxModel(channels.toTypedArray()) val lastUsedChannel = PropertiesComponent.getInstance().getValue(LAST_USED_CHANNEL) if (lastUsedChannel.notNullOrEmpty()) { channels.firstOrNull { it.toString() == lastUsedChannel } - ?.let { this.channelComboBox!!.model.selectedItem = it } + ?.let { this.channelComboBox.model.selectedItem = it } } - onChannelChanged?.let { it(this.channelComboBox?.selectedItem) } + onChannelChanged?.let { it(this.channelComboBox.selectedItem) } } fun setApisHandle(apisHandle: (Any?, List<*>) -> Unit) { @@ -141,7 +194,7 @@ class SuvApiExportDialog : ContextDialog() { if (actionContext.callInSwingUI { if (!disposed && !this.isFocused) { - this.apiList!!.requestFocus() + this.apiList.requestFocus() return@callInSwingUI false } else { return@callInSwingUI true @@ -173,8 +226,8 @@ class SuvApiExportDialog : ContextDialog() { } private fun onOK() { - val selectedChannel = this.channelComboBox!!.selectedItem - val selectedApis = GsonUtils.copy(this.apiList!!.selectedValuesList!!) as List<*> + val selectedChannel = this.channelComboBox.selectedItem + val selectedApis = GsonUtils.copy(this.apiList.selectedValuesList!!) as List<*> actionContext.runAsync { try { this.apisHandle!!(selectedChannel, selectedApis) diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/dialog/TriggerSupport.kt b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/dialog/TriggerSupport.kt new file mode 100644 index 000000000..e9d6d79c9 --- /dev/null +++ b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/dialog/TriggerSupport.kt @@ -0,0 +1,29 @@ +package com.itangcent.idea.plugin.dialog + +/** + * @author joe.wu + * @date 2023/11/14 9:20am + */ +class TriggerSupport { + private var trigger: String? = null + + fun withTrigger(trigger: String, cancelIfTriggerNotMatch: Boolean = true, action: () -> Unit) { + if (this.trigger != null && this.trigger != trigger) { + if (cancelIfTriggerNotMatch) { + return + } + action() + return + } + this.trigger = trigger + try { + action() + } finally { + this.trigger = null + } + } + + fun getTrigger(): String? { + return trigger + } +} \ No newline at end of file diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/swing/DefaultMessagesHelper.kt b/idea-plugin/src/main/kotlin/com/itangcent/idea/swing/DefaultMessagesHelper.kt index ad01cf3bb..232d49e7b 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/idea/swing/DefaultMessagesHelper.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/idea/swing/DefaultMessagesHelper.kt @@ -126,15 +126,17 @@ class DefaultMessagesHelper : MessagesHelper { override fun showChooseWithTipDialog( message: String?, - items: List?, - showAs: ((T) -> String?)?, - tipAs: ((T) -> String?)?, + items: List, + showAs: ((T) -> String)?, + tipAs: ((T) -> String)?, callBack: ((T?) -> Unit), ) { actionContext.runInSwingUI { val chooseWithTipDialog = ChooseWithTipDialog(SwingUtils.preferableWindow()) UIUtils.show(chooseWithTipDialog) - chooseWithTipDialog.updateItems(message, items, showAs, tipAs, callBack) + actionContext.runAsync { + chooseWithTipDialog.updateItems(message, items, showAs, tipAs, callBack) + } } } diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/swing/MessagesHelper.kt b/idea-plugin/src/main/kotlin/com/itangcent/idea/swing/MessagesHelper.kt index bc5f8781b..471c9f241 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/idea/swing/MessagesHelper.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/idea/swing/MessagesHelper.kt @@ -54,9 +54,9 @@ interface MessagesHelper { fun showChooseWithTipDialog( message: String?, - items: List?, - showAs: ((T) -> String?)?, - tipAs: ((T) -> String?)?, + items: List, + showAs: ((T) -> String)?, + tipAs: ((T) -> String)?, callBack: ((T?) -> Unit), ) @@ -74,9 +74,9 @@ interface MessagesHelper { fun MessagesHelper.showChooseWithTipDialog( message: String?, - items: List?, - showAs: ((T) -> String?)?, - tipAs: ((T) -> String?)?, + items: List, + showAs: ((T) -> String)?, + tipAs: ((T) -> String)?, ): T? { val valueHolder = ValueHolder() this.showChooseWithTipDialog(message, items, showAs, tipAs) { diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/utils/SwingUtils.kt b/idea-plugin/src/main/kotlin/com/itangcent/idea/utils/SwingUtils.kt index 5e0024d29..62a133cba 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/idea/utils/SwingUtils.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/idea/utils/SwingUtils.kt @@ -2,6 +2,7 @@ package com.itangcent.idea.utils import com.intellij.openapi.project.Project import com.intellij.openapi.wm.WindowManager +import com.itangcent.common.logger.Log import com.itangcent.common.utils.cast import com.itangcent.idea.swing.ActiveWindowProvider import com.itangcent.intellij.context.ActionContext @@ -13,7 +14,7 @@ import javax.swing.* import javax.swing.table.TableColumn import javax.swing.tree.* -object SwingUtils { +object SwingUtils : Log() { // Sets the focus on the specified Dialog component in the Swing UI thread. fun focus(dialog: Dialog) { ActionContext.getContext()!!.runInSwingUI { @@ -84,6 +85,12 @@ object SwingUtils { ?.let { return it } return null } + + fun logComponentDetails(component: JComponent, componentName: String) { + val location = component.location + val size = component.size + LOG.info("$componentName - Location: ($location) Dimensions: (${size.width} x ${size.height})") + } } // Returns true if the mouse event is a double-click event.