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.RenderAppendable;
024 import org.crsh.text.Renderer;
025 import org.crsh.text.Style;
026
027 import java.util.Arrays;
028 import java.util.concurrent.atomic.AtomicInteger;
029
030 class TableRenderer extends Renderer {
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 TableRowRenderer head;
058
059 /** . */
060 private TableRowRenderer tail;
061
062 TableRenderer(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 TableRowRenderer(this, row);
076 } else {
077 tail = tail.add(new TableRowRenderer(this, row));
078 }
079 }
080 }
081
082 private int getMaxColSize() {
083 int n = 0;
084 for (TableRowRenderer 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 (TableRowRenderer 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 (TableRowRenderer 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 (TableRowRenderer 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 (TableRowRenderer row = head;row != null;row = row.next()) {
142 for (int i = 0;i < row.getCols().size();i++) {
143 Renderer 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 (TableRowRenderer 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 (TableRowRenderer 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 Renderer.NULL.reader(width);
376 }
377 }
378 }