001    /*
002     * Copyright (C) 2012 eXo Platform SAS.
003     *
004     * This is free software; you can redistribute it and/or modify it
005     * under the terms of the GNU Lesser General Public License as
006     * published by the Free Software Foundation; either version 2.1 of
007     * the License, or (at your option) any later version.
008     *
009     * This software is distributed in the hope that it will be useful,
010     * but WITHOUT ANY WARRANTY; without even the implied warranty of
011     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012     * Lesser General Public License for more details.
013     *
014     * You should have received a copy of the GNU Lesser General Public
015     * License along with this software; if not, write to the Free
016     * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
017     * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
018     */
019    
020    package org.crsh.text.ui;
021    
022    import org.crsh.text.LineReader;
023    import org.crsh.text.LineRenderer;
024    import org.crsh.text.RenderAppendable;
025    import org.crsh.text.Style;
026    
027    import java.util.Arrays;
028    import java.util.concurrent.atomic.AtomicInteger;
029    
030    class TableLineRenderer extends LineRenderer {
031    
032      /** . */
033      final Layout columnLayout;
034    
035      /** . */
036      final Layout rowLayout;
037    
038      /** . */
039      final BorderStyle border;
040    
041      /** . */
042      final BorderStyle separator;
043    
044      /** . */
045      final Overflow overflow;
046    
047      /** . */
048      final Style.Composite style;
049    
050      /** Cell padding left. */
051      final int leftCellPadding;
052    
053      /** Cell padding right. */
054      final int rightCellPadding;
055    
056      /** . */
057      private TableRowLineRenderer head;
058    
059      /** . */
060      private TableRowLineRenderer tail;
061    
062      TableLineRenderer(TableElement table) {
063        this.rowLayout = table.getRowLayout();
064        this.columnLayout = table.getColumnLayout();
065        this.border = table.getBorder();
066        this.style = table.getStyle();
067        this.separator = table.getSeparator();
068        this.overflow = table.getOverflow();
069        this.leftCellPadding = table.getLeftCellPadding();
070        this.rightCellPadding = table.getRightCellPadding();
071    
072        //
073        for (RowElement row : table.getRows()) {
074          if (head == null) {
075            head = tail = new TableRowLineRenderer(this, row);
076          } else {
077            tail = tail.add(new TableRowLineRenderer(this, row));
078          }
079        }
080      }
081    
082      private int getMaxColSize() {
083        int n = 0;
084        for (TableRowLineRenderer row = head;row != null;row = row.next()) {
085          n = Math.max(n, row.getColsSize());
086        }
087        return n;
088      }
089    
090      @Override
091      public int getMinWidth() {
092        int minWidth = 0;
093        for (TableRowLineRenderer row = head;row != null;row = row.next()) {
094          minWidth = Math.max(minWidth, row.getMinWidth());
095        }
096        return minWidth + (border != null ? 2 : 0);
097      }
098    
099      @Override
100      public int getActualWidth() {
101        int actualWidth = 0;
102        for (TableRowLineRenderer row = head;row != null;row = row.next()) {
103          actualWidth = Math.max(actualWidth, row.getActualWidth());
104        }
105        return actualWidth + (border != null ? 2 : 0);
106      }
107    
108      @Override
109      public int getActualHeight(int width) {
110        if (border != null) {
111          width -= 2;
112        }
113        int actualHeight = 0;
114        for (TableRowLineRenderer row = head;row != null;row = row.next()) {
115          actualHeight += row.getActualHeight(width);
116        }
117        if (border != null) {
118          actualHeight += 2;
119        }
120        return actualHeight;
121      }
122    
123      @Override
124      public int getMinHeight(int width) {
125        return border != null ? 2 : 0;
126      }
127    
128      @Override
129      public LineReader reader(int width) {
130        return reader(width, 0);
131      }
132    
133      @Override
134      public LineReader reader(final int width, final int height) {
135    
136        int len = getMaxColSize();
137        int[] eltWidths = new int[len];
138        int[] eltMinWidths = new int[len];
139    
140        // Compute each column as is
141        for (TableRowLineRenderer row = head;row != null;row = row.next()) {
142          for (int i = 0;i < row.getCols().size();i++) {
143            LineRenderer renderable = row.getCols().get(i);
144            eltWidths[i] = Math.max(eltWidths[i], renderable.getActualWidth() + row.row.leftCellPadding + row.row.rightCellPadding);
145            eltMinWidths[i] = Math.max(eltMinWidths[i], renderable.getMinWidth() + row.row.leftCellPadding + row.row.rightCellPadding);
146          }
147        }
148    
149        // Note that we may have a different widths != eltWidths according to the layout algorithm
150        final int[] widths = columnLayout.compute(separator != null, width - (border != null ? 2 : 0), eltWidths, eltMinWidths);
151    
152        //
153        if (widths != null) {
154          // Compute new widths array
155          final AtomicInteger effectiveWidth = new AtomicInteger();
156          if (border != null) {
157            effectiveWidth.addAndGet(2);
158          }
159          for (int i = 0;i < widths.length;i++) {
160            effectiveWidth.addAndGet(widths[i]);
161            if (separator != null) {
162              if (i > 0) {
163                effectiveWidth.addAndGet(1);
164              }
165            }
166          }
167    
168          //
169          final int[] heights;
170          if (height > 0) {
171            // Apply vertical layout
172            int size = tail.getSize();
173            int[] actualHeights = new int[size];
174            int[] minHeights = new int[size];
175            for (TableRowLineRenderer row = head;row != null;row = row.next()) {
176              actualHeights[row.getIndex()] = row.getActualHeight(widths);
177              minHeights[row.getIndex()] = row.getMinHeight(widths);
178            }
179            heights = rowLayout.compute(false, height - (border != null ? 2 : 0), actualHeights, minHeights);
180            if (heights == null) {
181              return null;
182            }
183          } else {
184            heights = new int[tail.getSize()];
185            Arrays.fill(heights, -1);
186          }
187    
188          //
189          return new LineReader() {
190    
191            /** . */
192            TableRowReader rHead = null;
193    
194            /** . */
195            TableRowReader rTail = null;
196    
197            /** . */
198            int index = 0;
199    
200            /**
201             * 0 -> render top
202             * 1 -> render rows
203             * 2 -> render bottom
204             * 3 -> done
205             */
206            int status = border != null ? 0 : 1;
207    
208            {
209              // Add all rows
210              for (TableRowLineRenderer row = head;row != null;row = row.next()) {
211                if (row.getIndex() < heights.length) {
212                  int[] what;
213                  if (row.getColsSize() == widths.length) {
214                    what = widths;
215                  } else {
216    
217                    // I'm not sure this algorithm is great
218                    // perhaps the space should be computed or some kind of merge
219                    // that respect the columns should be done
220    
221                    // Redistribute space among columns
222                    what = new int[row.getColsSize()];
223                    for (int j = 0;j < widths.length;j++) {
224                      what[j % what.length] += widths[j];
225                    }
226    
227                    // Remove zero length columns to avoid issues
228                    int end = what.length;
229                    while (end > 0 && what[end - 1] == 0) {
230                      end--;
231                    }
232    
233                    //
234                    if (end != what.length) {
235                      what = Arrays.copyOf(what, end);
236                    }
237                  }
238                  TableRowReader next = row.renderer(what, heights[row.getIndex()]);
239                  if (rHead == null) {
240                    rHead = rTail = next;
241                  } else {
242                    rTail = rTail.add(next);
243                  }
244                } else {
245                  break;
246                }
247              }
248            }
249    
250            public boolean hasLine() {
251              switch (status) {
252                case 0:
253                case 2:
254                  return true;
255                case 1:
256                  while (rHead != null) {
257                    if (rHead.hasLine()) {
258                      return true;
259                    } else {
260                      rHead = rHead.next();
261                    }
262                  }
263    
264                  // Update status according to height
265                  if (height > 0) {
266                    if (border == null) {
267                      if (index == height) {
268                        status = 3;
269                      }
270                    } else {
271                      if (index == height - 1) {
272                        status = 2;
273                      }
274                    }
275                  } else {
276                    if (border != null) {
277                      status = 2;
278                    } else {
279                      status = 3;
280                    }
281                  }
282    
283                  //
284                  return status < 3;
285                default:
286                  return false;
287              }
288            }
289    
290            public void renderLine(RenderAppendable to) {
291              if (!hasLine()) {
292                throw new IllegalStateException();
293              }
294              switch (status) {
295                case 0:
296                case 2: {
297                  to.styleOff();
298                  to.append(border.corner);
299                  for (int i = 0;i < widths.length;i++) {
300                    if (widths[i] > 0) {
301                      if (separator != null && i > 0) {
302                        to.append(border.horizontal);
303                      }
304                      for (int j = 0;j < widths[i];j++) {
305                        to.append(border.horizontal);
306                      }
307                    }
308                  }
309                  to.append(border.corner);
310                  to.styleOn();
311                  for (int i = width - effectiveWidth.get();i > 0;i--) {
312                    to.append(' ');
313                  }
314                  status++;
315                  break;
316                }
317                case 1: {
318    
319                  //
320                  boolean sep = rHead != null && rHead.isSeparator();
321                  if (border != null) {
322                    to.styleOff();
323                    to.append(sep ? border.corner : border.vertical);
324                    to.styleOn();
325                  }
326    
327                  //
328                  if (style != null) {
329                    to.enterStyle(style);
330                  }
331    
332                  //
333                  if (rHead != null) {
334                    // Render row
335                    rHead.renderLine(to);
336                  } else {
337                    // Vertical padding
338                    for (int i = 0;i < widths.length;i++) {
339                      if (separator != null && i > 0) {
340                        to.append(separator.vertical);
341                      }
342                      for (int j = 0;j < widths[i];j++) {
343                        to.append(' ');
344                      }
345                    }
346                  }
347    
348                  //
349                  if (style != null) {
350                    to.leaveStyle();
351                  }
352    
353                  //
354                  if (border != null) {
355                    to.styleOff();
356                    to.append(sep ? border.corner : border.vertical);
357                    to.styleOn();
358                  }
359    
360                  // Padding
361                  for (int i = width - effectiveWidth.get();i > 0;i--) {
362                    to.append(' ');
363                  }
364                  break;
365                }
366                default:
367                  throw new AssertionError();
368              }
369    
370              // Increase vertical index
371              index++;
372            }
373          };
374        } else {
375          return LineRenderer.NULL.reader(width);
376        }
377      }
378    }