@@ -23,6 +23,7 @@ import java.awt.*
23
23
import java.awt.event.MouseAdapter
24
24
import java.awt.event.MouseEvent
25
25
import javax.swing.*
26
+ import javax.swing.border.EmptyBorder
26
27
27
28
class ViewHistoryAction : AnAction (
28
29
AutoDevBundle .message("action.view.history.text"),
@@ -59,21 +60,25 @@ class ViewHistoryAction : AnAction(
59
60
val relativeTime : String
60
61
)
61
62
63
+ // 跟踪正在删除的会话ID,避免冲突
64
+ private var deletingSessionId: String? = null
65
+
62
66
private inner class SessionListCellRenderer (
63
67
private val project : Project ,
64
68
private val onDelete : (ChatSessionHistory ) -> Unit
65
69
) : ListCellRenderer<SessionListItem> {
66
70
67
71
override fun getListCellRendererComponent (
68
- list : javax.swing. JList <out SessionListItem >,
72
+ list : JList <out SessionListItem >,
69
73
value : SessionListItem ,
70
74
index : Int ,
71
75
selected : Boolean ,
72
76
hasFocus : Boolean
73
77
): Component {
74
- // 创建主面板,使用 BorderLayout
78
+ // 创建主面板
75
79
val panel = JPanel (BorderLayout (10 , 0 ))
76
80
panel.border = JBUI .Borders .empty(4 , 8 )
81
+ panel.name = " CELL_PANEL_$index " // 添加唯一标识
77
82
78
83
// 设置背景颜色
79
84
if (selected) {
@@ -86,7 +91,7 @@ class ViewHistoryAction : AnAction(
86
91
panel.isOpaque = true
87
92
88
93
val sessionName = value.session.name
89
- val maxLength = 30 // 根据UI宽度调整最大长度
94
+ val maxLength = 30
90
95
val displayName = if (sessionName.length > maxLength) {
91
96
sessionName.substring(0 , maxLength - 3 ) + " ..."
92
97
} else {
@@ -95,6 +100,7 @@ class ViewHistoryAction : AnAction(
95
100
96
101
val contentPanel = JPanel (FlowLayout (FlowLayout .LEFT , 0 , 0 ))
97
102
contentPanel.isOpaque = false
103
+ contentPanel.name = " CONTENT_PANEL_$index " // 添加唯一标识
98
104
99
105
// 会话名称
100
106
val titleLabel = JLabel (displayName)
@@ -116,26 +122,41 @@ class ViewHistoryAction : AnAction(
116
122
contentPanel.add(titleLabel)
117
123
contentPanel.add(timeLabel)
118
124
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
120
139
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
+ }
134
153
}
135
- })
154
+ }
155
+
156
+ deleteButtonPanel.add(deleteButton, BorderLayout .CENTER )
136
157
137
158
panel.add(contentPanel, BorderLayout .CENTER )
138
- panel.add(deleteButton , BorderLayout .EAST )
159
+ panel.add(deleteButtonPanel , BorderLayout .EAST )
139
160
140
161
panel.preferredSize = Dimension (panel.preferredSize.width, 35 )
141
162
return panel
@@ -151,7 +172,11 @@ class ViewHistoryAction : AnAction(
151
172
return
152
173
}
153
174
175
+ var currentPopup: JBPopup ? = null
176
+
154
177
fun createAndShowPopup () {
178
+ currentPopup?.cancel()
179
+
155
180
val listItems = sessions.map { SessionListItem (it, formatRelativeTime(it.createdAt)) }
156
181
val jbList = JBList (listItems)
157
182
jbList.selectionMode = ListSelectionModel .SINGLE_SELECTION
@@ -168,7 +193,10 @@ class ViewHistoryAction : AnAction(
168
193
if (result == Messages .YES ) {
169
194
historyService.deleteSession(session.id)
170
195
sessions = historyService.getAllSessions().sortedByDescending { it.createdAt }
196
+ deletingSessionId = null // 重置删除标记
171
197
createAndShowPopup()
198
+ } else {
199
+ deletingSessionId = null // 用户取消删除,重置标记
172
200
}
173
201
}
174
202
@@ -180,7 +208,7 @@ class ViewHistoryAction : AnAction(
180
208
// 设置 Popup 的固定宽度和自适应高度
181
209
val popupWidth = 400
182
210
val maxPopupHeight = 400
183
- val itemHeight = 35 // 与fixedCellHeight一致
211
+ val itemHeight = 35
184
212
val calculatedHeight = (sessions.size * itemHeight + 20 ).coerceAtMost(maxPopupHeight)
185
213
186
214
scrollPane.preferredSize = Dimension (popupWidth, calculatedHeight)
@@ -194,6 +222,26 @@ class ViewHistoryAction : AnAction(
194
222
val selectedSession = listItems[index].session
195
223
onDeleteSession(selectedSession)
196
224
}
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
+ }
197
245
}
198
246
}
199
247
})
@@ -207,24 +255,57 @@ class ViewHistoryAction : AnAction(
207
255
.setCancelOnClickOutside(true )
208
256
.setCancelOnOtherWindowOpen(true )
209
257
210
- val popup: JBPopup = popupBuilder.createPopup()
258
+ currentPopup = popupBuilder.createPopup()
259
+ currentPopup?.setMinimumSize(Dimension (300 , 150 ))
260
+ currentPopup?.showInBestPositionFor(e.dataContext)
261
+ }
211
262
212
- // 设置 Popup 的最小尺寸
213
- popup.setMinimumSize( Dimension ( 300 , 150 ))
263
+ createAndShowPopup()
264
+ }
214
265
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
221
304
}
222
305
}
223
-
224
- popup.showInBestPositionFor(e.dataContext)
225
306
}
226
-
227
- createAndShowPopup()
307
+
308
+ return false
228
309
}
229
310
230
311
private fun loadSessionIntoSketch (project : Project , session : ChatSessionHistory ) {
0 commit comments