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 /**
023 * The columns layout computes the width of the columns in a table.
024 */
025 public abstract class ColumnLayout {
026
027 public static ColumnLayout rightToLeft() {
028 return RTL;
029 }
030
031 public static ColumnLayout weighted(int... weights) throws NullPointerException, IllegalArgumentException {
032 return new Weighted(weights);
033 }
034
035 abstract int[] compute(Border border, int width, int[] widths, int[] minWidths);
036
037 public static class Weighted extends ColumnLayout {
038
039 /** The weights. */
040 private final int[] weights;
041
042 /**
043 * Create a new weighted layout.
044 *
045 * @param weights the weights
046 * @throws NullPointerException if the weights argument is null
047 * @throws IllegalArgumentException if any weight is negative
048 */
049 private Weighted(int... weights) throws NullPointerException, IllegalArgumentException {
050 if (weights == null) {
051 throw new NullPointerException("No null weights accepted");
052 }
053 for (int weight : weights) {
054 if (weight < 0) {
055 throw new IllegalArgumentException("No negative weight accepted");
056 }
057 }
058 this.weights = weights.clone();
059 }
060
061 public int[] getWeights() {
062 return weights.clone();
063 }
064
065 @Override
066 int[] compute(Border border, int width, int[] widths, int[] minWidths) {
067
068 //
069 int len = Math.min(widths.length, weights.length);
070
071 //
072 for (int i = len;i > 0;i--) {
073
074 //
075 int totalWeight = 0;
076 int width2 = width;
077 for (int j = 0;j < i;j++) {
078 totalWeight += weights[j];
079 if (border != null) {
080 if (j == 0) {
081 width2 -= 2;
082 } else {
083 width2 -= 1;
084 }
085 }
086 }
087
088 //
089
090 // Compute the width of each column
091 int[] ret = new int[widths.length];
092 for (int j = 0;j < i;j++) {
093 int w = width2 * weights[j];
094 if (w < minWidths[j] * totalWeight) {
095 ret = null;
096 break;
097 } else {
098 ret[j] = w;
099 }
100 }
101
102 //
103 if (ret != null) {
104 // Error based scaling inspired from Brensenham algorithm:
105 // => sum of the weights == width
106 // => minimize error
107 // for instance with "foo","bar" scaled to 11 chars
108 // using floor + division gives "foobar_____"
109 // this methods gives "foo_bar____"
110 int err = 0;
111 for (int j = 0;j < ret.length;j++) {
112
113 // Compute base value
114 int value = ret[j] / totalWeight;
115
116 // Lower value
117 int lower = value * totalWeight;
118 int errLower = err + ret[j] - lower;
119
120 // Upper value
121 int upper = lower + totalWeight;
122 int errUpper = err + ret[j] - upper;
123
124 // We choose between lower/upper according to the accumulated error
125 // and we propagate the error
126 if (Math.abs(errLower) < Math.abs(errUpper)) {
127 ret[j] = value;
128 err = errLower;
129 } else {
130 ret[j] = value + 1;
131 err = errUpper;
132 }
133 }
134 return ret;
135 }
136 }
137
138 //
139 return null;
140 }
141 }
142
143 private static final ColumnLayout RTL = new ColumnLayout() {
144
145 @Override
146 int[] compute(Border border, int viewWidth, int[] colWidths, int[] colMinWidths) {
147
148 //
149 int[] ret = colWidths.clone();
150
151 //
152 int total = 0;
153 for (int colWidth : colWidths) {
154 if (border != null) {
155 total += 1;
156 }
157 total += colWidth;
158 }
159 if (border != null) {
160 total += 1;
161 }
162
163 //
164 int index = colMinWidths.length - 1;
165 while (total > viewWidth && index >= 0) {
166 int delta = total - viewWidth;
167 int bar = colWidths[index] - colMinWidths[index];
168 if (delta <= bar) {
169 int sub = Math.min(bar, delta);
170 total -= sub;
171 ret[index] -= sub;
172 } else {
173 int foo = colWidths[index];
174 if (border != null) {
175 foo++;
176 if (index == 0) {
177 foo++;
178 }
179 }
180 total -= foo;
181 ret[index] = 0;
182 }
183 index--;
184 }
185
186 //
187 return total > 0 ? ret : null;
188 }
189 };
190 /*
191
192 public static final ColumnLayout DISTRIBUTED = new ColumnLayout() {
193 @Override
194 public int[] compute(Border border, int width, int[] widths, int[] minWidths) {
195 int index = 0;
196 while (true) {
197
198 // Compute now the number of chars
199 boolean done = false;
200 int total = 0;
201 for (int i = 0;i < widths.length;i++) {
202 if (widths[i] >= minWidths[i]) {
203 total += widths[i];
204 if (border != null) {
205 if (done) {
206 total++;
207 }
208 else {
209 total += 2;
210 done = true;
211 }
212 }
213 }
214 }
215
216 // It's not valid
217 if (total == 0) {
218 return null;
219 }
220
221 //
222 int delta = width - total;
223
224 //
225 if (delta == 0) {
226 break;
227 } else if (delta > 0) {
228 switch (widths[index]) {
229 case 0:
230 break;
231 default:
232 widths[index]++;
233 break;
234 }
235 index = (index + 1) % widths.length;
236 } else {
237
238 // First try to remove from a column above min size
239 int found = -1;
240 for (int i = widths.length - 1;i >= 0;i--) {
241 int p = (index + i) % widths.length;
242 if (widths[p] > minWidths[p]) {
243 found = p;
244 if (--index < 0) {
245 index += widths.length;
246 }
247 break;
248 }
249 }
250
251 // If we haven't found a victim then we consider removing a column
252 if (found == -1) {
253 for (int i = widths.length - 1;i >= 0;i--) {
254 if (widths[i] > 0) {
255 found = i;
256 break;
257 }
258 }
259 }
260
261 // We couldn't find any solution we give up
262 if (found == -1) {
263 break;
264 } else {
265 widths[found]--;
266 }
267 }
268 }
269
270 //
271 return widths;
272 }
273 };
274 */
275 }