[英]Collapsable sentence within paragraph in java swing jeditorpane
I want to create collapsable
(or foldable
) sentences. 我想创建
collapsable
(或foldable
)的句子。 Importantly, they should be embedded into paragraphs and flow with the rest of the text. 重要的是,应将它们嵌入段落中,并与其余文本一起使用。 I'm working with Java / Swing, in a
JEditorpane
and an extension of the DefaultStyledDocument
. 我正在
JEditorpane
和DefaultStyledDocument
的扩展中使用Java / Swing。 For instance: 例如:
[-] This is sentence
[-]这句话
one.一。 [-] This is
[-] 这是
sentence two.第二句话。 [-] This is
[-] 这是
sentence three.第三句话。
When sentence two is collapsed, it becomes: 当第二句合拢时,它变为:
[-] This is sentence
[-]这句话
one.一。 [+] [-] This is
[+] [-]这是
sentence three.第三句话。
The webpage has an example of how to collapse a region of the document, but this region is not embedded into the paragraph. 网页上有一个示例,说明如何折叠文档的某个区域,但是该区域未嵌入段落中。 My problem is trying to decide which View to base this on.
我的问题是试图确定基于哪个视图。
If anybody could give me a pointer (or gut feeling) what I could best base SentenceView on (or some other tip to help me a long) I would highly appreciate that. 如果有人可以给我一个指针(或直觉),我可以最好地将SentenceView用作基础(或其他可以帮助我的技巧),我将非常感谢。
I'll try to answer my own question. 我将尝试回答我自己的问题。 After some code review of the standard Java Swing classes I decided that much of the functionality that I needed is implemented in
FlowView
and ParagraphView
. 在对标准Java Swing类进行一些代码审查之后,我决定在
FlowView
和ParagraphView
实现了我所需的许多功能。 Notably, there is the layoutRow
method of FlowStrategy
that is responsible for finding breakpoints and delivering rows of text. 值得注意的是,有
layoutRow
的方法FlowStrategy
是负责寻找断点,并提供文本行。 In my SentenceView
I have to do more or less exactly the same. 在我的
SentenceView
我必须做的大致相同。 The only difference is that I don't want to layout rows myself. 唯一的区别是我不想自己布置行。 Instead, my
SentenceView
needs to implement breakView
, so that the paragraph that contains the sentence can cut up the sentence in pieces and lay them out. 相反,我的
SentenceView
需要实现breakView
,以便包含句子的段落可以将句子切成碎片并进行布局。 Unfortunately, I cannot find a way in which I can re-use the function layoutRow
and I ended up copy/pasting a fair bit of that code. 不幸的是,我找不到一种可以重用
layoutRow
函数的layoutRow
,最终我复制/粘贴了相当一部分代码。
During the implementation of my (collapsable) SentenceView
I bumped into a number of Java 7 bugs. 在实现(可折叠)
SentenceView
我遇到了许多Java 7错误。 For instance, see the Java 7 line wrapping bug . 例如,请参见Java 7换行错误 。 Also, it seems to be ambiguous in Java whether the minimumSize is based on breakpoints at spaces (which are excellent break weights) or at characters (which are good break weights).
另外,在Java中,无论是否minimumSize是基于空格(极好的中断权重)或字符(极好的中断权重)的断点,在Java中似乎都是模棱两可的。
GlyphView
and LabelView
give their minimumSize on the basis of excellent break weights, but ParagraphView
mixes this with any breakpoints better than BadBreakWeight
(see the code for calculateMinorAxisRequirements
). GlyphView
和LabelView
在出色的折断权重的基础上给出了minimumSize,但是ParagraphView
将此断点与任何比BadBreakWeight
更好的断点混合在一起(请参见calculateMinorAxisRequirements
的代码)。 Which is rather confusing. 这相当令人困惑。
Anyway, let me highlight the most important parts of my implementation of SentenceView
. 无论如何,让我重点介绍
SentenceView
实现中最重要的部分。 I'm leaving out the actual foldable part as it is a bit fiddly and not the part I was struggling with. 我忽略了实际的可折叠部分,因为它有点笨拙,而不是我一直在努力的部分。 I'll focus on the
breakView
functionality. 我将重点介绍
breakView
功能。
public class SentenceView extends BoxView {
boolean containsStart;
boolean containsEnd;
public SentenceView(Element elem) {
super(elem, View.X_AXIS);
containsStart = true;
containsEnd = true;
}
...
I also have a getter isStart
for containsStart
and isEnd
for containsEnd
. 我也有一个getter
isStart
的containsStart
和isEnd
的containsEnd
。 The breakView
method is mostly copied, as I said: 正如我所说的,
breakView
方法主要是复制的:
public View breakView(int axis, int p0, float pos, float len) {
if (axis == View.Y_AXIS) return this;
if (p0 == getStartOffset() && len >= getPreferredSpan(axis)) return this;
float spanLeft = len;
int p = p0;
float x = pos;
int end = getEndOffset();
Row row = new Row(getElement());
TabExpander te = (this instanceof TabExpander) ? (TabExpander)this : null;
int breakWeight = BadBreakWeight;
float breakX = 0f;
float breakSpan = 0f;
int breakIndex = -1;
int n = 0;
if (p0 == getStartOffset() && isStart()) row.setContainsStart(true);
Vector<View> viewBuffer = new Vector<View>(10, 10);
while (p < end && spanLeft >= 0) {
View v = createView(p);
if (v == null) break;
int bw = v.getBreakWeight(axis, x, spanLeft);
if (bw >= ForcedBreakWeight) {
View w = v.breakView(axis, p, x, spanLeft);
if (w != null) {
viewBuffer.add(w);
} else if (n == 0) {
viewBuffer.add(v);
}
break;
} else if (bw >= breakWeight && bw > BadBreakWeight) {
breakWeight = bw;
breakX = x;
breakSpan = spanLeft;
breakIndex = n;
}
float chunkSpan;
if (v instanceof TabableView) {
chunkSpan = ((TabableView)v).getTabbedSpan(x, te);
} else {
chunkSpan = v.getPreferredSpan(axis);
}
if (isEnd() && v.getEndOffset() == end) {
if ((chunkSpan <= spanLeft) && (chunkSpan > spanLeft)) {
spanLeft = chunkSpan - 1;
}
}
if (chunkSpan > spanLeft && breakIndex >= 0) {
if (breakIndex < n) {
v = viewBuffer.get(breakIndex);
}
for (int i = n - 1; i >= breakIndex; i--) {
viewBuffer.remove(i);
}
v = v.breakView(axis, v.getStartOffset(), breakX, breakSpan);
}
spanLeft -= chunkSpan;
x += chunkSpan;
viewBuffer.add(v);
p = v.getEndOffset();
n++;
}
// cloning:
Vector<View> viewBuffer2 = new Vector<>();
for (int j = 0; j < viewBuffer.size(); j++) {
View v = viewBuffer.get(j);
if (v instanceof MyLabelView) {
v = (View) ((MyLabelView)v).createClone();
} else {
throw new RuntimeException("SentenceView can only contain MyLabelView");
}
viewBuffer2.add(v);
}
View[] views = new View[viewBuffer2.size()];
viewBuffer2.toArray(views);
row.replace(0, row.getViewCount(), views);
return row;
}
There are a few things to note here. 这里有几件事要注意。 First,
breakView
will return itself or a SentenceView.Row
. 首先,
breakView
将返回自身或SentenceView.Row
。 Secondly, I'm using a MyLabelView
instead of LabelView
, mostly because of the earlier mentioned line wrapping bug. 其次,我使用
MyLabelView
代替LabelView
,主要是因为前面提到了换行错误。 I'm still new to Java and Java Swing and I was struggling a bit with the Cloneable
interface and clone
is protected and final. 我对Java和Java Swing还是一个新手,我在
Cloneable
接口方面有些挣扎, clone
受到保护并且是最终的。 So, I just implemented createClone
to call clone
. 因此,我刚刚实现了
createClone
来调用clone
。 Not very elegant, but not my main worry either. 不是很优雅,但我也不主要担心。 The cloning I needed because the subviews get reparented when they are added to a
Row
. 我需要进行克隆,因为将子视图添加到
Row
时,它们会重新关联。 But, if at a later stage the original SentenceView
gets displayed again (because breaking is no longer necessary), this leads to problems. 但是,如果在以后的阶段再次显示原始的
SentenceView
(因为不再需要断开),则会导致问题。 I think a better way may be to reparent all the subviews back to this
but I could not figure out where to best do this. 我认为更好的方法可能是使所有子视图重新回到
this
但是我不知道在哪里最好地执行此操作。 I would welcome comments on this, though. 不过,我欢迎对此发表评论。 Also, in order to actually implement foldability, the
SentenceView
will have to share its foldable status with the created Rows
. 另外,为了实际实现可折叠性,
SentenceView
将必须与创建的Rows
共享其可折叠状态。 I did that by wrapping a boolean in an object and then sharing the object. 我通过在对象中包装一个布尔值然后共享该对象来做到这一点。
I'll give the following methods without code, as they are not too difficult or can be copied/amended from ParagraphView
. 我将提供以下没有代码的方法,因为它们不太困难或可以从
ParagraphView
复制/修改。
protected SizeRequirements calculateMajorAxisRequirements(int axis,
SizeRequirements r)
protected SizeRequirements calculateMinorAxisRequirements(int axis, SizeRequirements r)
public View createFragment(int p0, int p1)
protected View createView(int startOffset)
protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets, int[] spans) {
baselineLayout(targetSpan, axis, offsets, spans);
}
And then, finally, there is the Row
inner class: 最后,还有
Row
内部类:
class Row extends SentenceView {
Row(Element elem) {
super(elem);
containsStart = false;
containsEnd = false;
}
public float getAlignment(int axis) {
if (axis == View.X_AXIS) return 0;
return super.getAlignment(axis);
}
public AttributeSet getAttributes() {
View p = getParent();
return (p != null) ? p.getAttributes() : null;
}
protected int getViewIndexAtPosition(int pos) {
if(pos < getStartOffset() || pos >= getEndOffset())
return -1;
for(int counter = getViewCount() - 1; counter >= 0; counter--) {
View v = getView(counter);
if(pos >= v.getStartOffset() &&
pos < v.getEndOffset()) {
return counter;
}
}
return -1;
}
protected void loadChildren(ViewFactory f) {
}
}
That works. 这样可行。 It is a bit unfortunate that I ended up replicating quite a bit of existing code, but it isn't obvious to me how to implement
breakView
on the X axis in paragraphView
or how to find another way to do this. 不幸的是,我最终复制了很多现有代码,但是对我而言,如何在
paragraphView
breakView
的X轴上实现breakView
或如何找到另一种方式却并不明显。 Also, in order to implement foldability, a bit more work needs to be done. 另外,为了实现可折叠性,还需要做更多的工作。 The
paint
method needs to be altered to draw [-] in front (if containsStart
is true). 需要更改
paint
方法以在前面绘制[-](如果containsStart
为true)。 The minimumSize
and preferredSize
need to take into account this extra bit of space (I'm using insets for that). minimumSize
和preferredSize
需要考虑到这多余的空间(我使用的是inset)。 The SentenceView
and the Rows
it creates need to share the folded-status. SentenceView
及其创建的Rows
需要共享折叠状态。 If the view is folded, the minimumSize
and preferredSize
will be equal to the size of the [+] marker. 如果视图被折叠,则
minimumSize
和preferredSize
将等于[+]标记的大小。 There is some fiddling around with viewToModel
, modelToView
, getNextEastWestVisualPositionFrom
. 有一些摆弄周围有
viewToModel
, modelToView
, getNextEastWestVisualPositionFrom
。 This is all similar to Folding (collapsible) area in the JEditorPane/JTextPane . 这都类似于JEditorPane / JTextPane中的Folding(可折叠)区域 。 The only thing that I like a bit better in my code is the part in the mouse listener that checks whether the event took place inside a
SentenceView
as I'm avoiding the use of viewToModel
. 我希望代码中更好一点的唯一地方是鼠标侦听器中用于检查事件是否发生在
SentenceView
因为我避免使用viewToModel
。 So, let me give that as well: 所以,让我也给出:
MouseListener sentenceCollapse = new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
JEditorPane src = (JEditorPane)e.getSource();
View v = src.getUI().getRootView(src);
Insets ins = src.getInsets();
int x = ins.left;
int y = ins.top;
Point p = e.getPoint();
p.translate(-x, -y);
Rectangle alloc = new Rectangle(0,0,Short.MAX_VALUE,Short.MAX_VALUE);
while (v != null && !(v instanceof SentenceView)) {
View vChild = null;
int n = v.getViewCount();
for (int i = 0; i < n; i++) {
Rectangle r = v.getChildAllocation(i, alloc).getBounds();
if (r.contains(p)) {
vChild = v.getView(i);
alloc = (Rectangle) r.clone();
}
}
v = vChild;
}
if (v != null && v instanceof SentenceView) {
<<< unfold or fold the Sentence View >>>
src.repaint();
}
};
Hope this is useful. 希望这是有用的。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.