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 }