Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
F
Ftt_Common
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Administrator
Ftt_Common
Commits
beca36c3
Commit
beca36c3
authored
Sep 18, 2026
by
段廷瑞
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix: 点击空白弹出预览问题
parent
67a4bda7
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
93 additions
and
16 deletions
+93
-16
Tools.Agent.dll
AppHome/Tools.Agent.dll
+0
-0
Tools.Agent.xml
AppHome/Tools.Agent.xml
+26
-1
wMyAgent.cs
Tools.Agent/wMyAgent.cs
+67
-15
No files found.
AppHome/Tools.Agent.dll
View file @
beca36c3
No preview for this file type
AppHome/Tools.Agent.xml
View file @
beca36c3
...
@@ -2875,6 +2875,12 @@
...
@@ -2875,6 +2875,12 @@
<summary>
缩略图点击映射(v1.3.5):渲染序列里成功内联成 \pict 的图片绝对路径(渲染顺序),
<summary>
缩略图点击映射(v1.3.5):渲染序列里成功内联成 \pict 的图片绝对路径(渲染顺序),
来源 ChatRtfCache.LastImageSequence,与命中位置表按下标一一对应
</summary>
来源 ChatRtfCache.LastImageSequence,与命中位置表按下标一一对应
</summary>
</member>
</member>
<member
name=
"F:Tools.MyAgent.wMyAgent.ThumbHitRadius"
>
<summary>
缩略图字符命中半径(v1.3.7):超出即视为未命中(空白点击防线;见 NearestPosition)
</summary>
</member>
<member
name=
"F:Tools.MyAgent.wMyAgent.ThumbHeight"
>
<summary>
缩略图显示高(px,与 MarkdownRenderer.ThumbHeight 对齐;用于图片行行盒校验)
</summary>
</member>
<member
name=
"F:Tools.MyAgent.wMyAgent._thumbMarkers"
>
<member
name=
"F:Tools.MyAgent.wMyAgent._thumbMarkers"
>
<summary>
主命中通道(v1.3.5b):Text 流里零宽标记 U+200B 的位置(与 \pict 一一配对,随 RebuildAnswerRefs 重建)
</summary>
<summary>
主命中通道(v1.3.5b):Text 流里零宽标记 U+200B 的位置(与 \pict 一一配对,随 RebuildAnswerRefs 重建)
</summary>
</member>
</member>
...
@@ -2958,8 +2964,27 @@
...
@@ -2958,8 +2964,27 @@
主通道为零宽标记序数;标记缺失时回退 IRichEditOle 对象表序数。
主通道为零宽标记序数;标记缺失时回退 IRichEditOle 对象表序数。
</summary>
</summary>
</member>
</member>
<member
name=
"M:Tools.MyAgent.wMyAgent.IsClickOnImageBlock(System.Drawing.Point,System.Int32)"
>
<summary>
几何校验(v1.3.7):点击点是否真的落在图片块的行盒内。
<para>
为什么需要:
<c>
\pict
</c>
内联对象在字符流里只占一个零宽槽位,字符距离(
<see
cref=
"M:Tools.MyAgent.wMyAgent.NearestPosition(System.Collections.Generic.List{System.Int32},System.Int32,System.Int32@)"
/>
)
无法区分「点在图上」与「点在图片行右侧空白」—— 后者也会被归到该行行尾字符上。
故在字符命中通过后,再用像素几何确认一次:行盒(Y 在图片带内)+ 行尾右界(X 不越过行尾)。
</para>
<para>
X 右界取「该行最后一个字符的右缘」:图片行内容就是若干缩略图,最右一张的右缘之后即空白。
无法取到行尾坐标时**放行**(宁可少一次误拦,也不要让图片变成点不开)。
</para>
</summary>
</member>
<member
name=
"M:Tools.MyAgent.wMyAgent.GetLineIndexAt(System.Int32)"
>
<summary>
charIdx 所在行号(0 基);失败返回 0
</summary>
</member>
<member
name=
"M:Tools.MyAgent.wMyAgent.NearestPosition(System.Collections.Generic.List{System.Int32},System.Int32,System.Int32@)"
>
<member
name=
"M:Tools.MyAgent.wMyAgent.NearestPosition(System.Collections.Generic.List{System.Int32},System.Int32,System.Int32@)"
>
<summary>
位置表中距 charIdx 最近者(半径 ±2),命中返回其下标
</summary>
<summary>
位置表中距 charIdx 最近者(半径
<see
cref=
"F:Tools.MyAgent.wMyAgent.ThumbHitRadius"
/>
),命中返回其下标。
<para>
⚠ 必须设距离上界:
<b>
超出半径一律返回 false
</b>
。本通道位于
<c>
IsClickOnChar
</c>
闸门**之前**
(见 OnChatMouseUp 注释),因此它是空白点击的第一道也是唯一一道防线 —— 一旦退化为「取全局最近的一个」,
点在图片块右侧空白处(GetCharIndexFromPosition 归给行尾字符索引,而行尾恰是最后一张图的标记位)
就会被吸附到**最右边那张**缩略图上并弹出预览。
</para>
</summary>
</member>
</member>
<member
name=
"M:Tools.MyAgent.wMyAgent.OpenAnswerImage(System.String)"
>
<member
name=
"M:Tools.MyAgent.wMyAgent.OpenAnswerImage(System.String)"
>
<summary>
打开缩略图对应的大图(v1.3.5):直接走内置图片查看器
</summary>
<summary>
打开缩略图对应的大图(v1.3.5):直接走内置图片查看器
</summary>
...
...
Tools.Agent/wMyAgent.cs
View file @
beca36c3
...
@@ -119,6 +119,12 @@ namespace Tools.MyAgent
...
@@ -119,6 +119,12 @@ namespace Tools.MyAgent
/// 来源 ChatRtfCache.LastImageSequence,与命中位置表按下标一一对应</summary>
/// 来源 ChatRtfCache.LastImageSequence,与命中位置表按下标一一对应</summary>
private
List
<
string
>
_answerImageSeq
=
new
List
<
string
>();
private
List
<
string
>
_answerImageSeq
=
new
List
<
string
>();
/// <summary>缩略图字符命中半径(v1.3.7):超出即视为未命中(空白点击防线;见 NearestPosition)</summary>
private
const
int
ThumbHitRadius
=
2
;
/// <summary>缩略图显示高(px,与 MarkdownRenderer.ThumbHeight 对齐;用于图片行行盒校验)</summary>
private
const
int
ThumbHeight
=
72
;
/// <summary>主命中通道(v1.3.5b):Text 流里零宽标记 U+200B 的位置(与 \pict 一一配对,随 RebuildAnswerRefs 重建)</summary>
/// <summary>主命中通道(v1.3.5b):Text 流里零宽标记 U+200B 的位置(与 \pict 一一配对,随 RebuildAnswerRefs 重建)</summary>
private
List
<
int
>
_thumbMarkers
=
new
List
<
int
>();
private
List
<
int
>
_thumbMarkers
=
new
List
<
int
>();
...
@@ -678,18 +684,69 @@ namespace Tools.MyAgent
...
@@ -678,18 +684,69 @@ namespace Tools.MyAgent
return
false
;
return
false
;
}
}
/// <summary>位置表中距 charIdx 最近者(半径 ±2),命中返回其下标</summary>
/// <summary>
/// 几何校验(v1.3.7):点击点是否真的落在图片块的行盒内。
/// <para>为什么需要:<c>\pict</c> 内联对象在字符流里只占一个零宽槽位,字符距离(<see cref="NearestPosition"/>)
/// 无法区分「点在图上」与「点在图片行右侧空白」—— 后者也会被归到该行行尾字符上。
/// 故在字符命中通过后,再用像素几何确认一次:行盒(Y 在图片带内)+ 行尾右界(X 不越过行尾)。</para>
/// <para>X 右界取「该行最后一个字符的右缘」:图片行内容就是若干缩略图,最右一张的右缘之后即空白。
/// 无法取到行尾坐标时**放行**(宁可少一次误拦,也不要让图片变成点不开)。</para>
/// </summary>
private
bool
IsClickOnImageBlock
(
Point
click
,
int
charIdx
)
{
try
{
if
(
_rtfFallback
==
null
)
return
true
;
int
lineEnd
=
_rtfFallback
.
GetFirstCharIndexFromLine
(
GetLineIndexAt
(
charIdx
));
if
(
lineEnd
<
0
)
return
true
;
Point
pStart
=
_rtfFallback
.
GetPositionFromCharIndex
(
lineEnd
);
if
(
pStart
.
X
<
0
||
pStart
.
Y
<
0
)
return
true
;
// 行盒:以该行行首字符的行高为垂直带(图片与同行文字共享基线行)
int
lineHeight
=
Math
.
Max
(
_rtfFallback
.
Font
.
Height
,
ThumbHeight
);
const
int
bandPad
=
6
;
// 图片内联后行高可能大于字体行高,给一点宽容
if
(
Math
.
Abs
(
click
.
Y
-
pStart
.
Y
)
>
lineHeight
+
bandPad
)
return
false
;
// 行尾右界:该行最后一个字符(图片标记)的右缘 + 宽容
int
lastChar
=
_rtfFallback
.
GetFirstCharIndexFromLine
(
GetLineIndexAt
(
charIdx
)
+
1
);
int
endIdx
=
(
lastChar
>
0
?
lastChar
-
1
:
_rtfFallback
.
TextLength
-
1
);
if
(
endIdx
<
0
)
return
true
;
Point
pEnd
=
_rtfFallback
.
GetPositionFromCharIndex
(
endIdx
);
if
(
pEnd
.
X
<
0
||
pEnd
.
Y
<
0
||
pEnd
.
Y
!=
pStart
.
Y
)
return
true
;
// 取不到行尾坐标 → 放行
return
click
.
X
<=
pEnd
.
X
+
_rtfFallback
.
Font
.
Height
;
// 零宽标记无宽度 ⇒ 用字高近似其右缘
}
catch
{
return
true
;
}
}
/// <summary>charIdx 所在行号(0 基);失败返回 0</summary>
private
int
GetLineIndexAt
(
int
charIdx
)
{
try
{
if
(
_rtfFallback
==
null
||
charIdx
<
0
)
return
0
;
return
_rtfFallback
.
GetLineFromCharIndex
(
charIdx
);
}
catch
{
return
0
;
}
}
/// <summary>
/// 位置表中距 charIdx 最近者(半径 <see cref="ThumbHitRadius"/>),命中返回其下标。
/// <para>⚠ 必须设距离上界:<b>超出半径一律返回 false</b>。本通道位于 <c>IsClickOnChar</c> 闸门**之前**
/// (见 OnChatMouseUp 注释),因此它是空白点击的第一道也是唯一一道防线 —— 一旦退化为「取全局最近的一个」,
/// 点在图片块右侧空白处(GetCharIndexFromPosition 归给行尾字符索引,而行尾恰是最后一张图的标记位)
/// 就会被吸附到**最右边那张**缩略图上并弹出预览。</para>
/// </summary>
private
static
bool
NearestPosition
(
List
<
int
>
positions
,
int
charIdx
,
out
int
index
)
private
static
bool
NearestPosition
(
List
<
int
>
positions
,
int
charIdx
,
out
int
index
)
{
{
index
=
-
1
;
index
=
-
1
;
if
(
positions
==
null
||
positions
.
Count
==
0
)
return
false
;
if
(
positions
==
null
||
positions
.
Count
==
0
)
return
false
;
int
best
=
-
1
,
bestDist
=
3
;
int
best
=
-
1
,
bestDist
=
ThumbHitRadius
+
1
;
for
(
int
i
=
0
;
i
<
positions
.
Count
;
i
++)
for
(
int
i
=
0
;
i
<
positions
.
Count
;
i
++)
{
{
int
d
=
Math
.
Abs
(
positions
[
i
]
-
charIdx
);
int
d
=
Math
.
Abs
(
positions
[
i
]
-
charIdx
);
if
(
d
<
bestDist
)
{
bestDist
=
d
;
best
=
i
;
}
if
(
d
<
bestDist
)
{
bestDist
=
d
;
best
=
i
;
}
}
}
if
(
best
<
0
)
return
false
;
if
(
best
<
0
||
bestDist
>
ThumbHitRadius
)
return
false
;
// ← 超出半径不算命中(空白点击防线)
index
=
best
;
index
=
best
;
return
true
;
return
true
;
}
}
...
@@ -1487,18 +1544,12 @@ namespace Tools.MyAgent
...
@@ -1487,18 +1544,12 @@ namespace Tools.MyAgent
return
;
return
;
int
charIdx
=
_rtfFallback
.
GetCharIndexFromPosition
(
e
.
Location
);
int
charIdx
=
_rtfFallback
.
GetCharIndexFromPosition
(
e
.
Location
);
// 诊断(v1.3.5b 临时):Ctrl+点击 → 上报缩略图命中链路三要素(序列数/标记数/对象数/命中位置)
// 注意:MouseEventArgs 无 Control 属性(KeyEventArgs 才有),鼠标事件取修饰键用静态 Control.ModifierKeys
if
((
Control
.
ModifierKeys
&
Keys
.
Control
)
!=
0
)
{
AgentUtils
.
Toast
(
string
.
Format
(
"[诊断] seq={0} 标记={1} 对象={2} charIdx={3}"
,
_answerImageSeq
.
Count
,
_thumbMarkers
.
Count
,
_pictPositions
.
Count
,
charIdx
),
8000
);
return
;
}
// 缩略图(v1.3.5):\pict 内联对象直接点击放大 —— 须在 IsClickOnChar 之前判定:
// 缩略图(v1.3.5):\pict 内联对象直接点击放大 —— 须在 IsClickOnChar 之前判定:
// 对象字符锚点在图片左上角,点在图片中下部时 IsClickOnChar 的窄字符判定会把它当"空白点击"漏掉
// 对象字符锚点在图片左上角,点在图片中下部时 IsClickOnChar 的窄字符判定会把它当"空白点击"漏掉。
// v1.3.7:因提前于闸门,本通道自带两道防线 —— ① NearestPosition 的距离上界;
// ② IsClickOnImageBlock 的像素几何(拦截「图片行右侧空白」,那里 charIdx 会被归到行尾字符)。
string
thumbImage
;
string
thumbImage
;
if
(
TryGetAnswerImageAt
(
charIdx
,
out
thumbImage
))
if
(
TryGetAnswerImageAt
(
charIdx
,
out
thumbImage
)
&&
IsClickOnImageBlock
(
e
.
Location
,
charIdx
)
)
{
{
OpenAnswerImage
(
thumbImage
);
OpenAnswerImage
(
thumbImage
);
return
;
return
;
...
@@ -1532,9 +1583,10 @@ namespace Tools.MyAgent
...
@@ -1532,9 +1583,10 @@ namespace Tools.MyAgent
if
(
_mouseButtonDown
)
return
;
// 拖拽选择中,不切换悬停效果
if
(
_mouseButtonDown
)
return
;
// 拖拽选择中,不切换悬停效果
int
charIdx
=
_rtfFallback
.
GetCharIndexFromPosition
(
e
.
Location
);
int
charIdx
=
_rtfFallback
.
GetCharIndexFromPosition
(
e
.
Location
);
// 缩略图悬停(v1.3.5):与点击同因,须在 IsClickOnChar 闸门之前判定(对象锚点会窄字符误判为空白)
// 缩略图悬停(v1.3.5):与点击同因,须在 IsClickOnChar 闸门之前判定(对象锚点会窄字符误判为空白);
// v1.3.7:几何校验与点击同一口径(图片行右侧空白不再显示手型光标)
string
hoverImg
;
string
hoverImg
;
if
(
TryGetAnswerImageAt
(
charIdx
,
out
hoverImg
))
if
(
TryGetAnswerImageAt
(
charIdx
,
out
hoverImg
)
&&
IsClickOnImageBlock
(
e
.
Location
,
charIdx
)
)
{
{
if
(
charIdx
!=
_lastHoverChar
)
if
(
charIdx
!=
_lastHoverChar
)
{
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment