2013-04-28 3 views
0

Я хочу создать collapsable (или foldable) предложений. Важно отметить, что они должны быть встроены в абзацы и протекать вместе с остальной частью текста. Я работаю с Java/Swing, в JEditorpane и с расширением DefaultStyledDocument. Например:Складываемое предложение в параграфе в java swing jeditorpane

[-] Это предложение
один. [-] Это
предложение два. [-] Это
предложение три.

Когда предложение два свернут, он становится:

[-] Это предложение
один. [+] [-] Это
предложение три.

webpage есть пример того, как свернуть область документа, но этот регион не встраивается в пункте. Моя проблема заключается в том, чтобы решить, какой вид использовать для этого.

  • Создать SentenceView как расширение LabelView: проблема заключается в том, что LabelView не поддерживает разделы с различными атрибутами стиля. Возможно, я могу добавить это, но большинство представлений с несколькими дочерними элементами основаны на CompositeView.
  • Создайте SentenceView как расширение BoxView: может быть, но я не могу понять, как получить это встроенное в ParagraphView так, чтобы оно корректно текло с текстом.
  • Extend ParagraphView: Я мог бы создать какой-то сверхсознающий ParagraphView, который понимает, где предложения начинаются и заканчиваются, и обрабатывает все рушится. Вероятно, я мог бы заставить это работать, но это почти наверняка неправильный путь к делу.

Если бы кто-нибудь мог дать мне указатель (или ощущение кишки), что я мог бы лучше всего основать SentenceView (или какой-нибудь другой совет, чтобы помочь мне долго), я был бы очень признателен этому.

ответ

0

Я постараюсь ответить на свой вопрос. После некоторого обзора кода стандартных классов Java Swing я решил, что большая часть необходимых мне функций реализована в FlowView и ParagraphView. Примечательно, что существует метод layoutRowFlowStrategy, который отвечает за поиск контрольных точек и доставку строк текста. В моем 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(); 
    } 
}; 

Надеюсь, это полезно.