简体   繁体   English

java swing jeditorpane中段落内的可折叠句子

[英]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 . 我正在JEditorpaneDefaultStyledDocument的扩展中使用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. 我的问题是试图确定基于哪个视图。

  • Create SentenceView as extension of LabelView : problem is that LabelView does not support sections with different style attributes. 创建SentenceView作为LabelView的扩展名:问题是LabelView不支持具有不同样式属性的部分。 Maybe I can add this, but most Views with multiple children are based on CompositeView. 也许我可以添加它,但是大多数带有多个子视图的视图都是基于CompositeView的。
  • Create SentenceView as extension of BoxView : maybe, but I cannot figure out how to get this embedded into a ParagraphView in a way that it flows properly with the text. 创建SentenceView作为BoxView的扩展:也许可以,但是我无法弄清楚如何将其嵌入到ParagraphView中,使其与文本正确流动。
  • Extend ParagraphView : I could create some sort of super-aware ParagraphView that understands where sentences start and end and handles all the collapsing. 扩展ParagraphView :我可以创建某种超级感知的ParagraphView,它可以理解句子的起点和终点,并处理所有折叠。 I could probably get this to work, but this is almost surely the wrong-way-to-do-it. 我可能可以使它正常工作,但这几乎肯定是错误的方法。

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类进行一些代码审查之后,我决定在FlowViewParagraphView实现了我所需的许多功能。 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 ). GlyphViewLabelView在出色的折断权重的基础上给出了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 isStartcontainsStartisEndcontainsEnd 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). minimumSizepreferredSize需要考虑到这多余的空间(我使用的是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. 如果视图被折叠,则minimumSizepreferredSize将等于[+]标记的大小。 There is some fiddling around with viewToModel , modelToView , getNextEastWestVisualPositionFrom . 有一些摆弄周围有viewToModelmodelToViewgetNextEastWestVisualPositionFrom This is all similar to Folding (collapsible) area in the JEditorPane/JTextPane . 这都类似于JEdi​​torPane / 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM