Я постараюсь ответить на свой вопрос. После некоторого обзора кода стандартных классов Java Swing я решил, что большая часть необходимых мне функций реализована в FlowView
и ParagraphView
. Примечательно, что существует метод layoutRow
FlowStrategy
, который отвечает за поиск контрольных точек и доставку строк текста. В моем SentenceView
я должен сделать более или менее точно то же самое. Единственное различие заключается в том, что я не хочу самостоятельно компоновать строки. Вместо этого мой SentenceView
должен реализовать breakView
, так что абзац, содержащий предложение, может вырезать предложение по частям и выложить их. К сожалению, я не могу найти способ, которым я могу повторно использовать функцию layoutRow
, и я закончил копирование/вставка справедливой части этого кода.
Во время реализации моего (складывающегося) SentenceView
Я столкнулся с несколькими ошибками Java 7. Например, см. Java 7 line wrapping bug.Кроме того, в Java кажется двусмысленным: основан ли минимальный размер на контрольных точках в пространствах (которые являются превосходными весами разломов) или на символах (которые являются хорошими весами разрыва). GlyphView
и LabelView
дают минимальный размер на основе отличных весов, но 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
для containsStart
и isEnd
для 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
. Во-вторых, я использую MyLabelView
вместо LabelView
, в основном из-за ранее упомянутой ошибки обертывания строки. Я по-прежнему новичок в Java и Java Swing, и я немного борюсь с интерфейсом Cloneable
, а clone
является защищенным и окончательным. Итак, я только что применил createClone
для звонка clone
. Не очень элегантно, но не мое главное беспокойство. Клонирование, которое мне было необходимо, потому что subviews получаются повторно, когда они добавляются к . Но если на более позднем этапе снова появится оригинал 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);
}
И вот, наконец, есть внутренний класс:
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
). minimumSize
и preferredSize
необходимо учитывать этот дополнительный бит пространства (для этого я использую вставки). SentenceView
и Rows
, которые он создает, должны делиться сложенным статусом. Если вид сложен, то minimumSize
и preferredSize
будут равны размеру маркера [+]. Есть некоторые возиться с viewToModel
, modelToView
, getNextEastWestVisualPositionFrom
. Все это похоже на 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();
}
};
Надеюсь, это полезно.