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