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

时间:2013-04-28 10:02:46

标签: java swing jeditorpane collapsable

我想创建collapsable(或foldable)句子。重要的是,它们应嵌入到段落中,并与文本的其余部分一起流动。我正在使用Java / Swing,JEditorpaneDefaultStyledDocument的扩展名。 例如:

  
    

[ - ]这是句子     一。 [ - ]这是
    第二句。 [ - ]这是
    第三句。

  

当第二句被折叠时,它变为:

  
    

[ - ]这是句子     一。 [+] [ - ]这是
    第三句。

  

webpage有一个如何折叠文档区域的示例,但此区域未嵌入到段落中。我的问题是试图决定使用哪个View。

  • 创建SentenceView作为 LabelView 的扩展:问题是LabelView不支持具有不同样式属性的部分。也许我可以添加它,但大多数具有多个子项的视图都基于CompositeView。
  • 创建SentenceView作为 BoxView 的扩展:也许,但是我无法弄清楚如何将它嵌入到ParagraphView中,使其正确地与文本一起流动。
  • 扩展 ParagraphView :我可以创建某种超级意识的ParagraphView,它可以理解句子的开始和结束位置,并处理所有崩溃。我可能会让这个工作,但这几乎肯定是错误的方式。

如果有人能给我一个指针(或者直觉)我最好能够将SentenceView作为基础(或者其他一些提示帮助我很长时间),我将非常感激。

1 个答案:

答案 0 :(得分:0)

我会尝试回答我自己的问题。在对标准Java Swing类进行一些代码审查之后,我决定在FlowViewParagraphView中实现我需要的大部分功能。值得注意的是,layoutRow FlowStrategy方法负责查找断点并提供文本行。在我的SentenceView中,我必须或多或少完全相同。唯一的区别是我不想自己布局行。相反,我的SentenceView需要实现breakView,以便包含句子的段落可以将句子分成几部分并将其排除。不幸的是,我无法找到一种方法可以重新使用函数layoutRow,最后我复制/粘贴了相当多的代码。

在我的(可折叠的)SentenceView的实现过程中,我遇到了许多Java 7错误。例如,请参阅Java 7 line wrapping bug。此外,Java中似乎含糊不清的是,minimumSize是基于空格处的断点(具有极好的中断权重)还是基于字符(具有良好的中断权重)。 GlyphViewLabelView根据优秀的中断权重给出了minimumSize,但ParagraphView将此与任何比BadBreakWeight更好的断点混合使用(请参阅calculateMinorAxisRequirements的代码)。这是相当混乱的。

无论如何,让我重点介绍SentenceView实施中最重要的部分。我正在遗漏实际的可折叠部分,因为它有点繁琐,而不是我正在努力的部分。我将专注于breakView功能。

public class SentenceView extends BoxView {
    boolean containsStart;
    boolean containsEnd;
    public SentenceView(Element elem) {
        super(elem, View.X_AXIS);
        containsStart = true;
        containsEnd = true;
    }
    ...

isStart我也有containsStartisEndcontainsEnd。 正如我所说,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;
    }       

这里有几点需要注意。首先,breakView将返回自己或SentenceView.Row。其次,我使用MyLabelView而不是LabelView,主要是因为前面提到的换行错误。我还是Java和Java Swing的新手,我在Cloneable接口上苦苦挣扎,clone受保护且最终。所以,我刚刚实施createClone来致电clone。不是很优雅,但也不是我的主要担忧。我需要克隆,因为子视图在添加到Row时会被重新分配。但是,如果在稍后阶段原始SentenceView再次显示(因为不再需要断开),这会导致问题。我认为更好的方法可能是将所有子视图重新显示回this,但我无法确定最佳位置。不过,我欢迎对此发表评论。另外,为了实际实现可折叠性,SentenceView必须与创建的Rows共享其可折叠状态。我通过在对象中包装一个布尔值然后共享该对象来做到这一点。

我会在没有代码的情况下提供以下方法,因为它们不太难或可以从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);
}

然后,最后,有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) {
    }
}

有效。有点不幸的是,我最终复制了相当多的现有代码,但对我来说,如何在breakView中的X轴上实现paragraphView或者如何找到另一种方法并不明显做这个。此外,为了实现可折叠性,还需要做更多的工作。需要更改paint方法以在前面绘制[ - ](如果containsStart为真)。 minimumSizepreferredSize需要考虑这个额外的空间(我正在使用插图)。它创建的SentenceViewRows需要共享折叠状态。如果折叠视图,则minimumSizepreferredSize将等于[+]标记的大小。 viewToModelmodelToViewgetNextEastWestVisualPositionFrom有一些摆弄。这与Folding (collapsible) area in the JEditorPane/JTextPane类似。我在代码中唯一更喜欢的是鼠标监听器中的部分,它检查事件是否发生在SentenceView内,因为我避免使用viewToModel。那么,让我同意:

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();
    }
};

希望这很有用。