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 }