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 java.util.Arrays;
023
024 /**
025 * The layout computes the lengths of a list of contiguous cells.
026 */
027 public abstract class Layout {
028
029 public static Layout flow() {
030 return RTL;
031 }
032
033 public static Layout weighted(int... weights) throws NullPointerException, IllegalArgumentException {
034 return new Weighted(weights);
035 }
036
037 /**
038 * Computes the list of lengths for the specifid list of cells with the following constraints:
039 *
040 * <ul>
041 * <li>the sum of the returned array elements must be equals to the <code>length</code> argument</li>
042 * <li>a cell length should never be lesser than the provided minimum length</li>
043 * </ul>
044 *
045 * The returned array is the list of lengths from left to right, the array size may be less than the
046 * number of cells (i.e the size of the <code>actualLengths</code> and <code>minLengths</code> arguments). Missing
047 * cells are just be discarded and not part of the resulting layout. Array should contain only positive values,
048 * any zero length cell should be discarded. When cells must be discarded it must begin with the tail of the
049 * list, i.e it is not allowed to discard a cell that does not have a successor.
050 *
051 *
052 * @param spaced true if the cells are separated by one char
053 * @param length the length
054 * @param actualLengths the actual length : an estimation of what cell's desired length
055 * @param minLengths the minmum length : the length under which a cell cannot be rendered
056 * @return the list of length.
057 */
058 abstract int[] compute(boolean spaced, int length, int[] actualLengths, int[] minLengths);
059
060 public static class Weighted extends Layout {
061
062 /** The weights. */
063 private final int[] weights;
064
065 /**
066 * Create a new weighted layout.
067 *
068 * @param weights the weights
069 * @throws NullPointerException if the weights argument is null
070 * @throws IllegalArgumentException if any weight is negative
071 */
072 private Weighted(int... weights) throws NullPointerException, IllegalArgumentException {
073 if (weights == null) {
074 throw new NullPointerException("No null weights accepted");
075 }
076 for (int weight : weights) {
077 if (weight < 0) {
078 throw new IllegalArgumentException("No negative weight accepted");
079 }
080 }
081 this.weights = weights.clone();
082 }
083
084 public int[] getWeights() {
085 return weights.clone();
086 }
087
088 @Override
089 int[] compute(boolean spaced, int length, int[] actualLengths, int[] minLengths) {
090
091 //
092 int count = Math.min(actualLengths.length, weights.length);
093
094 //
095 for (int i = count;i > 0;i--) {
096
097 //
098 int totalLength = length;
099 int totalWeight = 0;
100 for (int j = 0;j < i;j++) {
101 totalWeight += weights[j];
102 if (spaced) {
103 if (j > 0) {
104 totalLength--;
105 }
106 }
107 }
108
109 // Compute the length of each cell
110 int[] ret = new int[i];
111 for (int j = 0;j < i;j++) {
112 int w = totalLength * weights[j];
113 if (w < minLengths[j] * totalWeight) {
114 ret = null;
115 break;
116 } else {
117 ret[j] = w;
118 }
119 }
120
121 //
122 if (ret != null) {
123 // Error based scaling inspired from Brensenham algorithm:
124 // => sum of the weights == length
125 // => minimize error
126 // for instance with "foo","bar" scaled to 11 chars
127 // using floor + division gives "foobar_____"
128 // this methods gives "foo_bar____"
129 int err = 0;
130 for (int j = 0;j < ret.length;j++) {
131
132 // Compute base value
133 int value = ret[j] / totalWeight;
134
135 // Lower value
136 int lower = value * totalWeight;
137 int errLower = err + ret[j] - lower;
138
139 // Upper value
140 int upper = lower + totalWeight;
141 int errUpper = err + ret[j] - upper;
142
143 // We choose between lower/upper according to the accumulated error
144 // and we propagate the error
145 if (Math.abs(errLower) < Math.abs(errUpper)) {
146 ret[j] = value;
147 err = errLower;
148 } else {
149 ret[j] = value + 1;
150 err = errUpper;
151 }
152 }
153 return ret;
154 }
155 }
156
157 //
158 return null;
159 }
160 }
161
162 private static final Layout RTL = new Layout() {
163
164 @Override
165 int[] compute(boolean spaced, int length, int[] actualLengths, int[] minLengths) {
166
167 //
168 int[] ret = actualLengths.clone();
169
170 //
171 int totalLength = 0;
172 for (int i = 0;i < actualLengths.length;i++) {
173 totalLength += actualLengths[i];
174 if (spaced && i > 0) {
175 totalLength++;
176 }
177 }
178
179 //
180 int index = minLengths.length - 1;
181 while (totalLength > length && index >= 0) {
182 int delta = totalLength - length;
183 int bar = actualLengths[index] - minLengths[index];
184 if (delta <= bar) {
185 // We are done
186 totalLength = length;
187 ret[index] -= delta;
188 } else {
189 int foo = actualLengths[index];
190 if (spaced) {
191 if (index > 0) {
192 foo++;
193 }
194 }
195 totalLength -= foo;
196 ret[index] = 0;
197 index--;
198 }
199 }
200
201 //
202 if (totalLength > 0) {
203 if (index == minLengths.length - 1) {
204 return ret;
205 } else {
206 return Arrays.copyOf(ret, index + 1);
207 }
208 } else {
209 return null;
210 }
211 }
212 };
213 /*
214
215 public static final ColumnLayout DISTRIBUTED = new ColumnLayout() {
216 @Override
217 public int[] compute(Border border, int width, int[] widths, int[] minWidths) {
218 int index = 0;
219 while (true) {
220
221 // Compute now the number of chars
222 boolean done = false;
223 int total = 0;
224 for (int i = 0;i < widths.length;i++) {
225 if (widths[i] >= minWidths[i]) {
226 total += widths[i];
227 if (border != null) {
228 if (done) {
229 total++;
230 }
231 else {
232 total += 2;
233 done = true;
234 }
235 }
236 }
237 }
238
239 // It's not valid
240 if (total == 0) {
241 return null;
242 }
243
244 //
245 int delta = width - total;
246
247 //
248 if (delta == 0) {
249 break;
250 } else if (delta > 0) {
251 switch (widths[index]) {
252 case 0:
253 break;
254 default:
255 widths[index]++;
256 break;
257 }
258 index = (index + 1) % widths.length;
259 } else {
260
261 // First try to remove from a column above min size
262 int found = -1;
263 for (int i = widths.length - 1;i >= 0;i--) {
264 int p = (index + i) % widths.length;
265 if (widths[p] > minWidths[p]) {
266 found = p;
267 if (--index < 0) {
268 index += widths.length;
269 }
270 break;
271 }
272 }
273
274 // If we haven't found a victim then we consider removing a column
275 if (found == -1) {
276 for (int i = widths.length - 1;i >= 0;i--) {
277 if (widths[i] > 0) {
278 found = i;
279 break;
280 }
281 }
282 }
283
284 // We couldn't find any solution we give up
285 if (found == -1) {
286 break;
287 } else {
288 widths[found]--;
289 }
290 }
291 }
292
293 //
294 return widths;
295 }
296 };
297 */
298 }