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    }