0

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

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

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

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

网页有一个如何折叠文档区域的示例,但该区域没有嵌入到段落中。我的问题是试图决定基于哪个视图。

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

如果有人可以给我一个指针(或直觉),我可以最好地将 SentenceView 建立在什么基础上(或其他一些可以帮助我长时间的提示),我将非常感激。

4

1 回答 1

0

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

在我的(可折叠的)实现过程中,我SentenceView遇到了一些 Java 7 错误。例如,请参阅Java 7 换行错误。此外,在 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;
    }
    ...

我也有一个 getter isStartforcontainsStartisEndfor containsEnd。正如我所说,该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. 其次,我使用的是 aMyLabelView而不是LabelView,主要是因为前面提到的换行错误。我还是 Java 和 Java Swing 的新手,我在Cloneable接口方面有点挣扎,并且clone是受保护的和最终的。所以,我只是实现createClone调用clone. 不是很优雅,但也不是我的主要担心。我需要克隆,因为子视图在添加到Row. 但是,如果在稍后阶段SentenceView再次显示原始文件(因为不再需要中断),这会导致问题。我认为更好的方法可能是将所有子视图重新设置为this但我不知道在哪里最好地做到这一点。不过,我欢迎对此发表评论。此外,为了实际实现可折叠性,SentenceView必须与 created 共享其可折叠状态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它需要共享折叠状态。如果视图被折叠,minimumSizeandpreferredSize将等于 [+] 标记的大小。有一些摆弄viewToModel, modelToView, getNextEastWestVisualPositionFrom。这都类似于JEdi​​torPane/JTextPane 中的折叠(可折叠)区域。在我的代码中,我唯一喜欢的一点是鼠标侦听器中检查事件是否发生在 a 内部的部分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();
    }
};

希望这是有用的。

于 2013-05-17T21:34:56.340 回答