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; 021 022 import java.util.ArrayList; 023 import java.util.Iterator; 024 025 /** 026 * Something that can be rendered within a context. 027 */ 028 public abstract class Renderer { 029 030 public static final Renderer NULL = new Renderer() { 031 @Override 032 public int getActualWidth() { 033 return 0; 034 } 035 @Override 036 public int getMinWidth() { 037 return 0; 038 } 039 @Override 040 public int getMinHeight(int width) { 041 return 0; 042 } 043 @Override 044 public int getActualHeight(int width) { 045 return 0; 046 } 047 @Override 048 public LineReader reader(int width) { 049 return new LineReader() { 050 public boolean hasLine() { 051 return false; 052 } 053 public void renderLine(RenderAppendable to) throws IllegalStateException { 054 throw new IllegalStateException(); 055 } 056 }; 057 } 058 }; 059 060 public static Renderer vertical(Iterable<? extends Renderer> renderers) { 061 Iterator<? extends Renderer> i = renderers.iterator(); 062 if (i.hasNext()) { 063 Renderer renderer = i.next(); 064 if (i.hasNext()) { 065 return new Composite(renderers); 066 } else { 067 return renderer; 068 } 069 } else { 070 return NULL; 071 } 072 } 073 074 /** 075 * Returns the element actual width. 076 * 077 * @return the actual width 078 */ 079 public abstract int getActualWidth(); 080 081 /** 082 * Returns the element minimum width. 083 * 084 * @return the minimum width 085 */ 086 public abstract int getMinWidth(); 087 088 /** 089 * Return the minimum height for the specified with. 090 * 091 * @param width the width 092 * @return the actual height 093 */ 094 public abstract int getMinHeight(int width); 095 096 /** 097 * Return the actual height for the specified with. 098 * 099 * @param width the width 100 * @return the minimum height 101 */ 102 public abstract int getActualHeight(int width); 103 104 /** 105 * Create a renderer for the specified width and height or return null if the element does not provide any output 106 * for the specified dimensions. The default implementation delegates to the {@link #reader(int)} method when the 107 * <code>height</code> argument is not positive otherwise it returns null. Subclasses should override this method 108 * when they want to provide content that can adapts to the specified height. 109 * 110 * @param width the width 111 * @param height the height 112 * @return the renderer 113 */ 114 public LineReader reader(int width, int height) { 115 if (height > 0) { 116 return null; 117 } else { 118 return reader(width); 119 } 120 } 121 122 /** 123 * Create a renderer for the specified width or return null if the element does not provide any output. 124 * 125 * @param width the width 126 * @return the renderer 127 */ 128 public abstract LineReader reader(int width); 129 130 /** 131 * Renders this object to the provided output. 132 * 133 * @param out the output 134 */ 135 public final void render(RenderAppendable out) { 136 LineReader renderer = reader(out.getWidth()); 137 if (renderer != null) { 138 while (renderer.hasLine()) { 139 renderer.renderLine(out); 140 out.append('\n'); 141 } 142 } 143 } 144 145 private static class Composite extends Renderer { 146 147 /** . */ 148 private final Iterable<? extends Renderer> renderers; 149 150 /** . */ 151 private final int actualWidth; 152 153 /** . */ 154 private final int minWidth; 155 156 private Composite(Iterable<? extends Renderer> renderers) { 157 158 int actualWidth = 0; 159 int minWidth = 0; 160 for (Renderer renderer : renderers) { 161 actualWidth = Math.max(actualWidth, renderer.getActualWidth()); 162 minWidth = Math.max(minWidth, renderer.getMinWidth()); 163 } 164 165 this.actualWidth = actualWidth; 166 this.minWidth = minWidth; 167 this.renderers = renderers; 168 } 169 170 @Override 171 public int getActualWidth() { 172 return actualWidth; 173 } 174 175 @Override 176 public int getMinWidth() { 177 return minWidth; 178 } 179 180 @Override 181 public int getActualHeight(int width) { 182 int actualHeight = 0; 183 for (Renderer renderer : renderers) { 184 actualHeight += renderer.getActualHeight(width); 185 } 186 return actualHeight; 187 } 188 189 @Override 190 public int getMinHeight(int width) { 191 return 1; 192 } 193 194 @Override 195 public LineReader reader(final int width, final int height) { 196 197 final Iterator<? extends Renderer> i = renderers.iterator(); 198 199 // 200 return new LineReader() { 201 202 /** . */ 203 private LineReader current; 204 205 /** . */ 206 private int index = 0; 207 208 public boolean hasLine() { 209 if (height > 0 && index >= height) { 210 return false; 211 } else { 212 if (current == null || !current.hasLine()) { 213 while (i.hasNext()) { 214 Renderer next = i.next(); 215 LineReader reader = next.reader(width); 216 if (reader != null && reader.hasLine()) { 217 current = reader; 218 return true; 219 } 220 } 221 return false; 222 } else { 223 return true; 224 } 225 } 226 } 227 228 public void renderLine(RenderAppendable to) throws IllegalStateException { 229 if (hasLine()) { 230 current.renderLine(to); 231 index++; 232 } else { 233 throw new IllegalStateException(); 234 } 235 } 236 }; 237 } 238 239 @Override 240 public LineReader reader(final int width) { 241 return reader(width, -1); 242 } 243 } 244 }