Skip to content

Commit 80d0590

Browse files
committed
fix(ui): improve session deletion and click handling
Enhance session history popup with better interaction handling: - Add tracking for session deletion to prevent conflicts - Replace JLabel with JButton for delete action with proper styling - Implement accurate click detection for UI components - Fix session selection vs deletion event conflicts - Add component
1 parent 9ab8ea3 commit 80d0590

File tree

1 file changed

+114
-33
lines changed

1 file changed

+114
-33
lines changed

core/src/main/kotlin/cc/unitmesh/devti/gui/toolbar/ViewHistoryAction.kt

Lines changed: 114 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import java.awt.*
2323
import java.awt.event.MouseAdapter
2424
import java.awt.event.MouseEvent
2525
import javax.swing.*
26+
import javax.swing.border.EmptyBorder
2627

2728
class ViewHistoryAction : AnAction(
2829
AutoDevBundle.message("action.view.history.text"),
@@ -59,21 +60,25 @@ class ViewHistoryAction : AnAction(
5960
val relativeTime: String
6061
)
6162

63+
// 跟踪正在删除的会话ID,避免冲突
64+
private var deletingSessionId: String? = null
65+
6266
private inner class SessionListCellRenderer(
6367
private val project: Project,
6468
private val onDelete: (ChatSessionHistory) -> Unit
6569
) : ListCellRenderer<SessionListItem> {
6670

6771
override fun getListCellRendererComponent(
68-
list: javax.swing.JList<out SessionListItem>,
72+
list: JList<out SessionListItem>,
6973
value: SessionListItem,
7074
index: Int,
7175
selected: Boolean,
7276
hasFocus: Boolean
7377
): Component {
74-
// 创建主面板,使用 BorderLayout
78+
// 创建主面板
7579
val panel = JPanel(BorderLayout(10, 0))
7680
panel.border = JBUI.Borders.empty(4, 8)
81+
panel.name = "CELL_PANEL_$index" // 添加唯一标识
7782

7883
// 设置背景颜色
7984
if (selected) {
@@ -86,7 +91,7 @@ class ViewHistoryAction : AnAction(
8691
panel.isOpaque = true
8792

8893
val sessionName = value.session.name
89-
val maxLength = 30 // 根据UI宽度调整最大长度
94+
val maxLength = 30
9095
val displayName = if (sessionName.length > maxLength) {
9196
sessionName.substring(0, maxLength - 3) + "..."
9297
} else {
@@ -95,6 +100,7 @@ class ViewHistoryAction : AnAction(
95100

96101
val contentPanel = JPanel(FlowLayout(FlowLayout.LEFT, 0, 0))
97102
contentPanel.isOpaque = false
103+
contentPanel.name = "CONTENT_PANEL_$index" // 添加唯一标识
98104

99105
// 会话名称
100106
val titleLabel = JLabel(displayName)
@@ -116,26 +122,41 @@ class ViewHistoryAction : AnAction(
116122
contentPanel.add(titleLabel)
117123
contentPanel.add(timeLabel)
118124

119-
val deleteButton = JLabel(AllIcons.Actions.Close)
125+
// 删除按钮面板,添加唯一标识
126+
val deleteButtonPanel = JPanel(BorderLayout())
127+
deleteButtonPanel.isOpaque = false
128+
deleteButtonPanel.name = "DELETE_BUTTON_PANEL_$index"
129+
130+
// 使用按钮代替标签,避免事件冲突
131+
val deleteButton = JButton()
132+
deleteButton.name = "DELETE_BUTTON_$index" // 添加唯一标识
133+
deleteButton.isOpaque = false
134+
deleteButton.isBorderPainted = false
135+
deleteButton.isContentAreaFilled = false
136+
deleteButton.isFocusPainted = false
137+
deleteButton.icon = AllIcons.Actions.Close
138+
deleteButton.rolloverIcon = AllIcons.Actions.CloseHovered
120139
deleteButton.cursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)
121-
deleteButton.border = JBUI.Borders.emptyLeft(8)
122-
deleteButton.addMouseListener(object : MouseAdapter() {
123-
override fun mouseClicked(e: MouseEvent) {
124-
e.consume() // 防止事件传播到列表
125-
onDelete(value.session)
126-
}
127-
128-
override fun mouseEntered(e: MouseEvent) {
129-
deleteButton.icon = AllIcons.Actions.CloseHovered
130-
}
131-
132-
override fun mouseExited(e: MouseEvent) {
133-
deleteButton.icon = AllIcons.Actions.Close
140+
deleteButton.preferredSize = Dimension(16, 16)
141+
deleteButton.toolTipText = "删除会话"
142+
143+
// 为删除按钮添加事件标记,便于在列表的鼠标监听器中识别
144+
deleteButton.putClientProperty("sessionId", value.session.id)
145+
146+
deleteButton.addActionListener { e ->
147+
e.source?.let { source ->
148+
if (source is JButton) {
149+
// 将正在删除的会话ID记录下来
150+
deletingSessionId = value.session.id
151+
onDelete(value.session)
152+
}
134153
}
135-
})
154+
}
155+
156+
deleteButtonPanel.add(deleteButton, BorderLayout.CENTER)
136157

137158
panel.add(contentPanel, BorderLayout.CENTER)
138-
panel.add(deleteButton, BorderLayout.EAST)
159+
panel.add(deleteButtonPanel, BorderLayout.EAST)
139160

140161
panel.preferredSize = Dimension(panel.preferredSize.width, 35)
141162
return panel
@@ -151,7 +172,11 @@ class ViewHistoryAction : AnAction(
151172
return
152173
}
153174

175+
var currentPopup: JBPopup? = null
176+
154177
fun createAndShowPopup() {
178+
currentPopup?.cancel()
179+
155180
val listItems = sessions.map { SessionListItem(it, formatRelativeTime(it.createdAt)) }
156181
val jbList = JBList(listItems)
157182
jbList.selectionMode = ListSelectionModel.SINGLE_SELECTION
@@ -168,7 +193,10 @@ class ViewHistoryAction : AnAction(
168193
if (result == Messages.YES) {
169194
historyService.deleteSession(session.id)
170195
sessions = historyService.getAllSessions().sortedByDescending { it.createdAt }
196+
deletingSessionId = null // 重置删除标记
171197
createAndShowPopup()
198+
} else {
199+
deletingSessionId = null // 用户取消删除,重置标记
172200
}
173201
}
174202

@@ -180,7 +208,7 @@ class ViewHistoryAction : AnAction(
180208
// 设置 Popup 的固定宽度和自适应高度
181209
val popupWidth = 400
182210
val maxPopupHeight = 400
183-
val itemHeight = 35 // 与fixedCellHeight一致
211+
val itemHeight = 35
184212
val calculatedHeight = (sessions.size * itemHeight + 20).coerceAtMost(maxPopupHeight)
185213

186214
scrollPane.preferredSize = Dimension(popupWidth, calculatedHeight)
@@ -194,6 +222,26 @@ class ViewHistoryAction : AnAction(
194222
val selectedSession = listItems[index].session
195223
onDeleteSession(selectedSession)
196224
}
225+
} else if (e.button == MouseEvent.BUTTON1 && e.clickCount == 1) { // 左键单击
226+
val index = jbList.locationToIndex(e.point)
227+
if (index >= 0) {
228+
// 检查点击位置是否在删除按钮区域
229+
val cell = jbList.getCellBounds(index, index)
230+
val cellComponent = jbList.cellRenderer.getListCellRendererComponent(
231+
jbList, listItems[index], index, false, false
232+
)
233+
234+
// 在该方法之外,显式检查是否点击了删除按钮
235+
val isDeleteButtonClick = isClickOnDeleteButton(e.point, cellComponent, cell)
236+
237+
// 如果不是点击删除按钮,或者当前没有正在处理的删除操作,则处理选择逻辑
238+
if (!isDeleteButtonClick && deletingSessionId != listItems[index].session.id) {
239+
jbList.selectedIndex = index
240+
val selectedSession = listItems[index].session
241+
currentPopup?.closeOk(null)
242+
loadSessionIntoSketch(project, selectedSession)
243+
}
244+
}
197245
}
198246
}
199247
})
@@ -207,24 +255,57 @@ class ViewHistoryAction : AnAction(
207255
.setCancelOnClickOutside(true)
208256
.setCancelOnOtherWindowOpen(true)
209257

210-
val popup: JBPopup = popupBuilder.createPopup()
258+
currentPopup = popupBuilder.createPopup()
259+
currentPopup?.setMinimumSize(Dimension(300, 150))
260+
currentPopup?.showInBestPositionFor(e.dataContext)
261+
}
211262

212-
// 设置 Popup 的最小尺寸
213-
popup.setMinimumSize(Dimension(300, 150))
263+
createAndShowPopup()
264+
}
214265

215-
jbList.addListSelectionListener {
216-
if (!it.valueIsAdjusting && jbList.selectedIndex != -1) {
217-
val selectedIndex = jbList.selectedIndex
218-
popup.closeOk(null)
219-
val selectedSession = listItems[selectedIndex].session
220-
loadSessionIntoSketch(project, selectedSession)
266+
// 检查点击是否在删除按钮上
267+
private fun isClickOnDeleteButton(point: Point, cellComponent: Component, cellBounds: Rectangle): Boolean {
268+
if (cellComponent is JPanel) {
269+
val pointInCell = Point(
270+
point.x - cellBounds.x,
271+
point.y - cellBounds.y
272+
)
273+
274+
// 递归查找所有子组件,检查是否点击在DELETE_BUTTON开头的组件上
275+
return findComponentAtPoint(cellComponent, pointInCell, "DELETE_BUTTON")
276+
}
277+
return false
278+
}
279+
280+
// 递归查找指定前缀名称的组件
281+
private fun findComponentAtPoint(container: Container, point: Point, namePrefix: String): Boolean {
282+
// 检查当前容器是否匹配
283+
if (container.name?.startsWith(namePrefix) == true) {
284+
return true
285+
}
286+
287+
// 递归检查所有子组件
288+
for (i in 0 until container.componentCount) {
289+
val child = container.getComponent(i)
290+
if (!child.isVisible || !child.bounds.contains(point)) {
291+
continue
292+
}
293+
294+
// 检查子组件名称
295+
if (child.name?.startsWith(namePrefix) == true) {
296+
return true
297+
}
298+
299+
// 递归检查子容器
300+
if (child is Container) {
301+
val childPoint = Point(point.x - child.x, point.y - child.y)
302+
if (findComponentAtPoint(child, childPoint, namePrefix)) {
303+
return true
221304
}
222305
}
223-
224-
popup.showInBestPositionFor(e.dataContext)
225306
}
226-
227-
createAndShowPopup()
307+
308+
return false
228309
}
229310

230311
private fun loadSessionIntoSketch(project: Project, session: ChatSessionHistory) {

0 commit comments

Comments
 (0)