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
020package org.crsh.text.ui;
021
022import org.crsh.text.LineReader;
023import org.crsh.text.LineRenderer;
024import org.crsh.text.RenderAppendable;
025import org.crsh.text.Style;
026
027import java.util.Arrays;
028import java.util.concurrent.atomic.AtomicInteger;
029
030class 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}